home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / C / Applications / Newswatcher 2.0b22 / NW Source / Source / article.c < prev    next >
Encoding:
Text File  |  1994-12-06  |  91.8 KB  |  2,597 lines  |  [TEXT/MMCC]

  1. ------------
  2.     OpenArticleOnAnyServer
  3.     
  4.     Open an article on any server, perhaps a different server from the
  5.     default one (for opening nntp URLs).
  6.     
  7.     Entry:    host = address of news server.
  8.             port = port number.
  9.             newsgroup = newsgroup name.
  10.             artNumber = article number.
  11.             
  12.     Exit:    function result = error code (fnfErr if article not found).
  13. ----------------------------------------------------------------------------*/
  14.  
  15. OSErr OpenArticleOnAnyServer (char *host, short port, 
  16.     char *newsgroup, long artNumber)
  17. {
  18.     WindowPtr newWind;
  19.     OSErr err = noErr;
  20.  
  21.     err = GetArticleAndMakeNewWindow(host, port, newsgroup, artNumber, 
  22.         nil, nil, false, nil, true, &newWind);
  23.     if (err != noErr) return err;
  24.     return newWind == nil ? fnfErr : noErr;
  25. }
  26.  
  27.  
  28.  
  29. /*----------------------------------------------------------------------------
  30.     FormFileName 
  31.     
  32.     Form the default file name for saving an article.
  33.             
  34.     Entry:    wind = pointer to article window.
  35.     
  36.     Exit:    fileName = default file name.
  37. ----------------------------------------------------------------------------*/
  38.  
  39. static void FormFileName (WindowPtr wind, Str31 fileName)
  40. {
  41.     Str255 title;
  42.     
  43.     GetWTitle(wind, title);
  44.     if (title[0] > 0 && title[1] == checkMark) {
  45.         title[0]--;
  46.         BlockMoveData(title+2, title+1, title[0]);
  47.     }
  48.     MakeLegalFileName(title, fileName);
  49. }
  50.  
  51.  
  52.  
  53. /*----------------------------------------------------------------------------
  54.     PresentStandardArticleSaveFileDialog 
  55.     
  56.     Present the standard save file dialog.
  57.             
  58.     Entry:    fileName = default file name.
  59.             *saveEncodedText = default value for save encoded text option.
  60.             *saveThreadsToSeparateFiles = default value for save threads
  61.                 to separate files option.
  62.     
  63.     Exit:    function result = error code.
  64.             *fSpec = file spec.
  65.             *scriptTag = script code.
  66.             *saveEncodedText = true to save encoded text.
  67.             *saveThreadsToSeparateFiles = true to save threads to separate
  68.                 files.
  69. ----------------------------------------------------------------------------*/
  70.  
  71. OSErr PresentStandardArticleSaveFileDialog (Str31 fileName, FSSpec *fSpec, 
  72.     ScriptCode *scriptTag, Boolean *saveEncodedText,
  73.     Boolean *saveThreadsToSeparateFiles)
  74. {
  75.     StandardFileReply reply;
  76.     Str255 prompt;
  77.     
  78.     GetPString(kStrSaveArticlePrompt, prompt);
  79.     MyStandardPutArticle(prompt, fileName, &reply, 
  80.         saveEncodedText, saveThreadsToSeparateFiles,
  81.         gPrefs.savedArtDefaultFolder ? gPrefs.savedArtDefaultFolderAlias : nil);
  82.     if (!reply.sfGood) return userCanceledErr;
  83.     *fSpec = reply.sfFile;
  84.     *scriptTag = reply.sfScript;
  85.     return noErr;
  86. }
  87.  
  88.  
  89.  
  90. /*----------------------------------------------------------------------------
  91.     PresentStandardArticleGetFileDialog 
  92.     
  93.     Present the standard get file dialog.
  94.     
  95.     Entry:    *saveEncodedText = default value of save encoded text option.
  96.     
  97.     Exit:    function result = error code.
  98.             *fSpec = file spec.
  99.             *saveEncodedText = true to save encoded text.
  100. ----------------------------------------------------------------------------*/
  101.  
  102. OSErr PresentStandardArticleGetFileDialog (FSSpec *fSpec, Boolean *saveEncodedText)
  103. {
  104.     StandardFileReply reply;
  105.     
  106.     MyStandardAppendArticle(nil, 1, "TEXT", &reply, saveEncodedText, 
  107.         gPrefs.savedArtDefaultFolder ? gPrefs.savedArtDefaultFolderAlias : nil);
  108.     if (!reply.sfGood) return userCanceledErr;
  109.     *fSpec = reply.sfFile;
  110.     return noErr;
  111. }
  112.  
  113.  
  114.  
  115. /*----------------------------------------------------------------------------
  116.     SaveTextToArticleFile 
  117.     
  118.     Save text to a file.
  119.             
  120.     Entry:    text = handle to text to be saved.
  121.             start = offset in text of first char to save.
  122.             end = offset in text following last char char to save.
  123.             fSpec = pointer to file spec.
  124.             scriptTag = script code.
  125.             append = true to append text to end of file.
  126.     
  127.     Exit:    function result = error code.
  128. ----------------------------------------------------------------------------*/
  129.  
  130. static OSErr SaveTextToArticleFile (Handle text, long start, long end,
  131.      FSSpec *fSpec, ScriptCode scriptTag, Boolean append)
  132. {
  133.     OSErr err = noErr;
  134.     long length;
  135.     short refNum = 0;
  136.     char state;
  137.     char *separator, *p;
  138.     Boolean needCR, empty;
  139.  
  140.     state = MyHGetState(text);
  141.  
  142.     err = OpenDataForkWriteCreateIfMissing(fSpec, gPrefs.savedArtCreator, 'TEXT',
  143.         scriptTag, append, &refNum, &empty);
  144.     if (err != noErr) goto exit;
  145.     
  146.     if (append && !empty) {
  147.         separator = 
  148.             "\r----------------------------------------------------------------------\r\r";
  149.         length = strlen(separator);
  150.         err = MyFSWriteNoCache(refNum, &length, separator, GiveTime);
  151.         if (err != noErr) goto exit;
  152.     }
  153.  
  154.     length = end - start;
  155.     MyHLock(text);
  156.     
  157.     for (p = *text + end - 1; p >= *text + start && *p == CR; p--) /* do nothing */;
  158.     p++;
  159.     if (p < *text + end) {
  160.         needCR = false;
  161.         length = p + 1 - *text - start;
  162.     } else {
  163.         needCR = true;
  164.     }
  165.     
  166.     err = MyFSWriteNoCache(refNum, &length, *text + start, GiveTime);
  167.     if (err != noErr) goto exit;
  168.     if (needCR) {
  169.         length = 1;
  170.         err = MyFSWriteNoCache(refNum, &length, CRSTR, GiveTime);
  171.         if (err != noErr) goto exit;
  172.     }
  173.     MyHSetState(text, state);
  174.     MyFSClose(refNum, GiveTime);
  175.     return noErr;
  176.     
  177. exit:
  178.  
  179.     MyHSetState(text, state);
  180.     if (refNum != 0) MyFSClose(refNum, GiveTime);
  181.     return err;
  182. }
  183.  
  184.  
  185.  
  186. /*----------------------------------------------------------------------------
  187.     GetAndSaveFullTextToArticleFile 
  188.     
  189.     Get the full text of an article with attached binaries and save it 
  190.     to a file.
  191.             
  192.     Entry:    wind = pointer to article window.
  193.             fSpec = pointer to file spec.
  194.             scriptTag = script code.
  195.             append = true to append text to end of file.
  196.     
  197.     Exit:    function result = error code.
  198. ----------------------------------------------------------------------------*/
  199.  
  200. static OSErr GetAndSaveFullTextToArticleFile (WindowPtr wind,
  201.      FSSpec *fSpec, ScriptCode scriptTag, Boolean append)
  202. {
  203.     OSErr err = noErr;
  204.     long length;
  205.     short refNum = 0;
  206.     char *separator;
  207.     Boolean empty;
  208.     TWindow **info, **parentInfo;
  209.     WindowPtr parent;
  210.     TSubject **subjectArray;
  211.     short index;
  212.     CStr255 groupName;
  213.     long number;
  214.     TAttachedFileKind partKind;
  215.     Handle msgId;
  216.     char state;
  217.  
  218.     err = DisplayStatusMessageNumber(kStrGettingAndSavingArticle);
  219.     if (err != noErr) return err;
  220.  
  221.     err = OpenDataForkWriteCreateIfMissing(fSpec, gPrefs.savedArtCreator, 'TEXT',
  222.         scriptTag, append, &refNum, &empty);
  223.     if (err != noErr) goto exit;
  224.     
  225.     if (append && !empty) {
  226.         separator = 
  227.             "\r----------------------------------------------------------------------\r\r";
  228.         length = strlen(separator);
  229.         err = MyFSWriteNoCache(refNum, &length, separator, GiveTime);
  230.         if (err != noErr) goto exit;
  231.     }
  232.     
  233.     info = (TWindow**)GetWRefCon(wind);
  234.     parent = (**info).parentWindow;
  235.     
  236.     if (parent == nil) {
  237.     
  238.         msgId = (**info).msgId;
  239.         state = MyHGetState(msgId);
  240.         MyHLockHi(msgId);
  241.         err = CopyArticleToFile(nil, 0, *msgId, "ARTICLE", refNum,
  242.             false, false, &partKind);
  243.         MyHSetState(msgId, state);
  244.         if (err != noErr) goto exit;
  245.     
  246.     } else {
  247.     
  248.         parentInfo = (TWindow**)GetWRefCon(parent);
  249.         subjectArray = (**parentInfo).subjectArray;
  250.         index = (**info).parentSubject;
  251.         strcpy(groupName, *gGroupNames + (**parentInfo).groupNameOffset);
  252.         number = (*subjectArray)[index].number;
  253.         err = CopyArticleToFile(groupName, number, nil, "ARTICLE", refNum,
  254.             false, false, &partKind);
  255.         if (err != noErr) goto exit;
  256.     
  257.     }
  258.  
  259.     MyFSClose(refNum, GiveTime);
  260.     return noErr;
  261.     
  262. exit:
  263.  
  264.     if (refNum != 0) MyFSClose(refNum, GiveTime);
  265.     return err;
  266. }
  267.  
  268.  
  269.  
  270. /*----------------------------------------------------------------------------
  271.     CheckArticleFileExists 
  272.     
  273.     Check to see if an article file already exists in the default article
  274.     file save directory. If it does, present an alert asking the user to
  275.     append, replace, cancel, or pick a new name. If the "Append if file
  276.     alread exists" preference is turned on, automatically append.
  277.             
  278.     Entry:    fileName = file name.
  279.             *saveEncodedText = default value for save encoded text
  280.                 option.
  281.             *saveThreadsToSeparateFiles = default value for save threads
  282.                 to separate files option.
  283.     
  284.     Exit:    function result = error code.
  285.             *fSpec = file spec.
  286.             *scriptTag = script code.
  287.             *append = true to append.
  288.             *saveEncodedText = true to save encoded text.
  289.             *saveThreadsToSeparateFiles = true to save threads to 
  290.                 separate files.
  291. ----------------------------------------------------------------------------*/
  292.  
  293. OSErr CheckArticleFileExists (Str31 fileName, FSSpec *fSpec, 
  294.     ScriptCode *scriptTag, Boolean *append, 
  295.     Boolean *saveEncodedText, Boolean *saveThreadsToSeparateFiles)
  296. {
  297.     OSErr err = noErr;
  298.     DialogPtr dlg = nil;
  299.     Boolean valid;
  300.     short item;
  301.  
  302.     *append = false;
  303.     ValidateSavedFolderAlias(gPrefs.savedArtDefaultFolderAlias,
  304.         &fSpec->vRefNum, &fSpec->parID, &valid);
  305.     if (!valid) {
  306.         ErrorMessageNumber(kStrDefaultArtFoldNotFound);
  307.         err = PresentStandardArticleSaveFileDialog(fileName, fSpec, scriptTag, 
  308.             saveEncodedText, saveThreadsToSeparateFiles);
  309.         if (err != noErr) return err;
  310.         return noErr;
  311.     }
  312.     *scriptTag = smSystemScript;
  313.     CopyPascalString(fSpec->name, fileName);
  314.     *append = gPrefs.appendIfFileAlreadyExists;
  315.     err = FileOrFolderExists(fSpec);
  316.     if (err == fnfErr) return noErr;
  317.     if (err != noErr) return err;
  318.     if (gPrefs.appendIfFileAlreadyExists) return noErr;
  319.     *append = false;
  320.     err = MyGetNewDialog(kArticleFileExistsAlert, 1, 2, &dlg);
  321.     if (err != noErr) return err;
  322.     ParamText(fileName, "\p", "\p", "\p");
  323.     SetItemKeyEquivalent(dlg, 3, 'R');
  324.     SetItemKeyEquivalent(dlg, 4, 'A');
  325.     SetItemKeyEquivalent(dlg, 5, 'D');
  326.     SysBeep(0);
  327.     MyModalDialog(dlg, gDialogFilterUPP, &item);
  328.     if (item == 5) DlgFlashButton(dlg, 4);
  329.     err = DoClose(dlg);
  330.     if (err != noErr) return err;
  331.     switch (item) {
  332.         case 1: /* Pick a New Name */
  333.             err = PresentStandardArticleSaveFileDialog(fileName, fSpec, 
  334.                 scriptTag, saveEncodedText, saveThreadsToSeparateFiles);
  335.             if (err != noErr) return err;
  336.             return noErr;
  337.         case 2: /* Cancel */
  338.             return userCanceledErr;
  339.         case 3: /* Replace */
  340.             return noErr;
  341.         case 4: /* Append */
  342.         case 5: /* Append (off screen, Cmd-D equiv.) */
  343.             *append = true;
  344.             return noErr;
  345.     }
  346.     return noErr;
  347. }
  348.  
  349.  
  350.  
  351. /*----------------------------------------------------------------------------
  352.     SaveArticleWindToFile 
  353.     
  354.     Save article window text to a file.
  355.             
  356.     Entry:    wind = pointer to article window.
  357.             modifiers = modifiers field from event record.
  358.             fSpec = pointer to file spec.
  359.             scriptTag = script code.
  360.             append = true to append text to end of file.
  361.             saveEncodedText = true to save encoded text.
  362.     
  363.     Exit:    function result = error code.
  364. ----------------------------------------------------------------------------*/
  365.  
  366. static OSErr SaveArticleWindToFile (WindowPtr wind, short modifiers, 
  367.     FSSpec *fSpec, ScriptCode scriptTag, Boolean append,
  368.     Boolean saveEncodedText)
  369. {
  370.     TWindow **info;
  371.     long start, end;
  372.     Handle text;
  373.     TEHandle theTE;
  374.     Boolean shift;
  375.     
  376.     MyICReadSharedPrefs(kICeditorHelper);
  377.     
  378.     info = (TWindow**)GetWRefCon(wind);
  379.     shift = (modifiers & shiftKey) != 0;
  380.     
  381.     if (!shift && saveEncodedText && (**info).attachedFile) {
  382.     
  383.         return GetAndSaveFullTextToArticleFile(wind, fSpec, scriptTag, append);
  384.     
  385.     } else {
  386.     
  387.         if (shift) {
  388.             theTE = (**info).theTE;
  389.             text = (**theTE).hText;
  390.             start = (**theTE).selStart;
  391.             end = (**theTE).selEnd;
  392.         } else {
  393.             text = (**info).fullText;
  394.             start = 0;
  395.             end = MyGetHandleSize(text);
  396.         }
  397.         return SaveTextToArticleFile(text, start, end, fSpec, scriptTag, append);
  398.         
  399.     }
  400. }
  401.  
  402.  
  403.  
  404. /*----------------------------------------------------------------------------
  405.     CheckForMissingParts 
  406.     
  407.     Check for missing parts before trying to extract binaries for an article
  408.     window.
  409.             
  410.     Entry:    wind = pointer to article window.
  411.     
  412.     Exit:    function result = error code.
  413. ----------------------------------------------------------------------------*/
  414.  
  415. static OSErr CheckForMissingParts (WindowPtr wind)
  416. {
  417.     TWindow **info, **parentInfo;
  418.     WindowPtr parent;
  419.     TSubject **subjectArray;
  420.     short index;
  421.     
  422.     info = (TWindow**)GetWRefCon(wind);
  423.     parent = (**info).parentWindow;
  424.     parentInfo = (TWindow**)GetWRefCon(parent);
  425.     subjectArray = (**parentInfo).subjectArray;
  426.     index = (**info).parentSubject;
  427.     if ((*subjectArray)[index].incomplete) {
  428.         NoteMessageNumber(kStrDecodePartsMissing);
  429.         return userCanceledErr;
  430.     }
  431.     return noErr;
  432. }
  433.  
  434.  
  435.  
  436. /*----------------------------------------------------------------------------
  437.     PresentStandardDownloadSaveFileDialog 
  438.     
  439.     Present the standard file dialog for the temp file for extracting
  440.     binaries.
  441.             
  442.     Entry:    fileName = default file name.
  443.     
  444.     Exit:    function result = error code.
  445.             *fSpec = file spec.
  446.             *scriptTag = script code.
  447. ----------------------------------------------------------------------------*/
  448.  
  449. OSErr PresentStandardDownloadSaveFileDialog (Str31 fileName, FSSpec *fSpec,
  450.     ScriptCode *scriptTag)
  451. {
  452.     StandardFileReply reply;
  453.     Str255 prompt;
  454.     
  455.     MyICReadSharedPrefs(kICDownloadFolder);
  456.     
  457.     GetPString(kStrDownloadTempFilePrompt, prompt); 
  458.     MyStandardPutFile(prompt, fileName, &reply,
  459.         gPrefs.savedBinDefaultFolder ? gPrefs.savedBinDefaultFolderAlias : nil);
  460.     if (!reply.sfGood) return userCanceledErr;
  461.     *fSpec = reply.sfFile;
  462.     *scriptTag = reply.sfScript;
  463.     return noErr;
  464. }
  465.  
  466.  
  467.  
  468. /*----------------------------------------------------------------------------
  469.     CheckDownloadFileExists 
  470.     
  471.     Check to see if a temp file already exists in the download directory. 
  472.     If it does, make the temp file name unique.
  473.             
  474.     Entry:    fileName = default file name.
  475.     
  476.     Exit:    function result = error code.
  477.             *fSpec = file spec.
  478.             *scriptTag = script code.
  479. ----------------------------------------------------------------------------*/
  480.  
  481. OSErr CheckDownloadFileExists (Str31 fileName, FSSpec *fSpec, 
  482.     ScriptCode *scriptTag)
  483. {
  484.     OSErr err = noErr;
  485.     Boolean valid;
  486.     
  487.     MyICReadSharedPrefs(kICDownloadFolder);
  488.  
  489.     ValidateSavedFolderAlias(gPrefs.savedBinDefaultFolderAlias,
  490.         &fSpec->vRefNum, &fSpec->parID, &valid);
  491.     if (!valid) {
  492.         ErrorMessageNumber(kStrDownloadFoldNotFound);
  493.         err = PresentStandardDownloadSaveFileDialog(fileName, fSpec, scriptTag);
  494.         if (err != noErr) return err;
  495.         return noErr;
  496.     }
  497.     *scriptTag = smSystemScript;
  498.     CopyPascalString(fSpec->name, fileName);
  499.     return MakeFileNameUnique(fSpec, nil);
  500. }
  501.  
  502.  
  503.  
  504. /*----------------------------------------------------------------------------
  505.     SaveBinariesToFile 
  506.     
  507.     Save binaries to a file for an article.
  508.             
  509.     Entry:    wind = pointer to subject window.
  510.             index = index in subject array of article.
  511.             fSpec = pointer to file spec.
  512.             scriptTag = script code.
  513.             artNum = article number being saved (1,2,3,...numArts).
  514.             numArts = total number of articles being saved.
  515.     
  516.     Exit:    function result = error code.
  517.             *fileKind = kind of attached file.
  518. ----------------------------------------------------------------------------*/
  519.  
  520. static OSErr SaveBinariesToFile (WindowPtr wind, short index, FSSpec *fSpec,
  521.     ScriptCode scriptTag, short artNum, short numArts, 
  522.     TAttachedFileKind *fileKind)
  523. {
  524.     TWindow **info;
  525.     TSubject **subjectArray;
  526.     short numPartsToSave;
  527.     CStr255 statusMsg, statusMsgFormat;
  528.     Boolean append = false;
  529.     short partNum, nextIndex, threadHeadIndex, n;
  530.     Handle text = nil;
  531.     long number;
  532.     CStr255 groupName;
  533.     Boolean empty, hasParts;
  534.     OSErr err = noErr;
  535.     short refNum;
  536.     TAttachedFileKind partKind;
  537.     Cell theCell;
  538.     short numMarked;
  539.     
  540.     MyICReadSharedPrefs(kICeditorHelper);
  541.     
  542.     info = (TWindow**)GetWRefCon(wind);
  543.     subjectArray = (**info).subjectArray;
  544.     strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
  545.     
  546.     *fileKind = kNoAttachedFile;
  547.     
  548.     threadHeadIndex = (*subjectArray)[index].threadHeadIndex;
  549.     
  550.     if ((*subjectArray)[index].numParts == 0x7fff) {
  551.         hasParts = false;
  552.         numPartsToSave = 1;
  553.         partNum = 1;
  554.     } else {
  555.         hasParts = true;
  556.         numPartsToSave = (*subjectArray)[index].numParts;
  557.         index = threadHeadIndex;
  558.     }
  559.     
  560.     if (numArts > 1) {
  561.         GetCString(kStrGettingAndSavingArtAofBPartNofM, statusMsgFormat);
  562.     } else if (hasParts) {
  563.         GetCString(kStrGettingAndSavingPartNofM, statusMsgFormat);
  564.     } else {
  565.         GetCString(kStrGettingAndSavingFile, statusMsg);
  566.     }
  567.     
  568.     err = OpenDataForkWriteCreateIfMissing(fSpec, gPrefs.savedArtCreator, 'TEXT',
  569.         scriptTag, false, &refNum, &empty);
  570.     if (err != noErr) return err;
  571.     
  572.     while (true) {
  573.         if (hasParts) {
  574.             while (index != -1 && (*subjectArray)[index].partNum == 0x7fff) 
  575.                 index = (*subjectArray)[index].nextInThread;
  576.             if (index == -1) break;
  577.             partNum = (*subjectArray)[index].partNum;
  578.             while (true) {
  579.                 nextIndex = (*subjectArray)[index].nextInThread;
  580.                 while (nextIndex != -1 && (*subjectArray)[nextIndex].partNum == 0x7fff) 
  581.                     nextIndex = (*subjectArray)[nextIndex].nextInThread;
  582.                 if (nextIndex == -1) break;
  583.                 if ((*subjectArray)[nextIndex].partNum != partNum) break;
  584.                 index = nextIndex;
  585.             }
  586.         }
  587.         if (index == -1) break;
  588.         if (numArts > 1) {
  589.             sprintf(statusMsg, statusMsgFormat, artNum, numArts, partNum, numPartsToSave);
  590.         } else if (hasParts) {
  591.             sprintf(statusMsg, statusMsgFormat, partNum, numPartsToSave);
  592.         }
  593.         err = DisplayStatusMessage(statusMsg);
  594.         if (err != noErr) goto exit;
  595.         number = (*subjectArray)[index].number;
  596.         
  597.         err = CopyArticleToFile(groupName, number, nil, "ARTICLE", refNum,
  598.             false, false, &partKind);
  599.         if (err != noErr) goto exit;
  600.         if (partKind == kArtNotOnServer) {
  601.             *fileKind = kArtNotOnServer;
  602.             goto exit;
  603.         } else if (*fileKind == kNoAttachedFile) {
  604.             *fileKind = partKind;
  605.         }
  606.         if (!hasParts) break;
  607.         index = (*subjectArray)[index].nextInThread;
  608.         
  609.     }
  610.     
  611.     if (*fileKind != kArtNotOnServer && *fileKind != kNoAttachedFile) {
  612.         if (FindParentCell(wind, threadHeadIndex, &theCell)) {
  613.             MarkSubjectCell(wind, theCell, true, &numMarked);
  614.             if (!(*subjectArray)[threadHeadIndex].collapsed) {
  615.                 n = (*subjectArray)[threadHeadIndex].threadLength;
  616.                 while (--n > 0) {
  617.                     theCell.v++;
  618.                     MarkSubjectCell(wind, theCell, true, &numMarked);
  619.                 }
  620.             }
  621.         }
  622.     }
  623.         
  624. exit:
  625.     
  626.     MyFSClose(refNum, GiveTime);
  627.     return err;
  628. }
  629.  
  630.  
  631.  
  632. /*----------------------------------------------------------------------------
  633.     RunDecodeHelperProgram 
  634.     
  635.     Run the helper program to decode the temp file.
  636.             
  637.     Entry:    fSpec = pointer to file spec for temp file.
  638.             fileKind = kind of attached binary.
  639.     
  640.     Exit:    function result = error code.
  641. ----------------------------------------------------------------------------*/
  642.  
  643. OSErr RunDecodeHelperProgram (FSSpec *fSpec, TAttachedFileKind fileKind)
  644. {
  645.     OSErr err = noErr;
  646.     FSSpec appSpec, fSpecWithSuffix;
  647.     Boolean running;
  648.     ProcessSerialNumber psn;
  649.     CStr255 fmt, msg;
  650.     char *suffix;
  651.     StringPtr helperName;
  652.     
  653.     helperName = fileKind == kBinHex ? gPrefs.hqxHelperName : gPrefs.uuHelperName;
  654.  
  655.     err = FindAppFromSig(fileKind == kBinHex ? gPrefs.hqxHelper : gPrefs.uuHelper, 
  656.         &appSpec, &running, &psn);
  657.     if (err != noErr) goto exit1;
  658.     
  659.     fSpecWithSuffix = *fSpec;
  660.     suffix = fileKind == kBinHex ? ".hqx" : ".uu";
  661.     err = MakeFileNameUnique(&fSpecWithSuffix, suffix);
  662.     if (err != noErr) goto exit3;
  663.     
  664.     err = FSpRename(fSpec, fSpecWithSuffix.name);
  665.     if (err != noErr) goto exit3;
  666.     
  667.     err = LaunchAppWithDoc(running, &appSpec, &psn, &fSpecWithSuffix, 0,
  668.         launchContinue | launchNoFileFlags | launchDontSwitch);
  669.     if (err != noErr) goto exit2;
  670.  
  671.     return noErr;
  672.     
  673. exit1:
  674.  
  675.     if (err == fnfErr) {
  676.         GetCString(kStrCantFindDecodeHelper, fmt);
  677.         p2cstr(helperName);
  678.         sprintf(msg, fmt, helperName);
  679.         c2pstr((char*)helperName);
  680.         DoCantFindHelperDialog(msg);
  681.         return userCanceledErr;
  682.     } else {
  683.         goto exit3;
  684.     }
  685.     
  686. exit2:
  687.  
  688.     if (err == memFullErr || err == memFragErr || err == appMemFullErr) {
  689.         GetCString(kStrDecodeHelperNoMem, fmt);
  690.         p2cstr(helperName);
  691.         sprintf(msg, fmt, helperName);
  692.         c2pstr((char*)helperName);
  693.         ErrorMessage(msg);
  694.         return userCanceledErr;
  695.     } else {
  696.         goto exit3;
  697.     }
  698.     
  699. exit3:
  700.  
  701.     GetCString(kStrDecodeHelperUnexpectedErr, fmt);
  702.     p2cstr(helperName);
  703.     sprintf(msg, fmt, err, helperName);
  704.     c2pstr((char*)helperName);
  705.     ErrorMessage(msg);
  706.     return userCanceledErr;
  707. }
  708.  
  709.  
  710.  
  711. /*----------------------------------------------------------------------------
  712.     ExtractBinaries 
  713.     
  714.     Extract binaries for an article or thread.
  715.             
  716.     Entry:    wind = pointer to subject window.
  717.             index = index of article or part in subject array.
  718.             artNum = article number being saved (1,2,3,...numArts).
  719.             numArts = total number of articles being saved.
  720.             modifiers = modifiers field from event record.
  721.     
  722.     Exit:    function result = error code.
  723.             *fileKind = kind of attached file.
  724. ----------------------------------------------------------------------------*/
  725.  
  726. OSErr ExtractBinaries (WindowPtr wind, short index, short artNum, 
  727.     short numArts, short modifiers, TAttachedFileKind *fileKind)
  728. {
  729.     static Str31 fileName;
  730.     static FSSpec fSpec;
  731.     static ScriptCode scriptTag;
  732.     OSErr err = noErr;
  733.     
  734.     MyICReadSharedPrefs(kICeditorHelper);
  735.     
  736.     if (artNum == 1) {
  737.         GetPString(kStrTempFileName, fileName);
  738.         if (gPrefs.savedBinDefaultFolder && (modifiers & optionKey) == 0) {
  739.             err = CheckDownloadFileExists(fileName, &fSpec, &scriptTag);
  740.             if (err != noErr) return err;
  741.         } else {
  742.             err = PresentStandardDownloadSaveFileDialog(fileName, &fSpec, &scriptTag);
  743.             if (err != noErr) return err;
  744.         }
  745.     } else {
  746.         CopyPascalString(fSpec.name, fileName);
  747.         err = MakeFileNameUnique(&fSpec, nil);
  748.         if (err != noErr) return err;
  749.     }
  750.     
  751.     err = SaveBinariesToFile(wind, index, &fSpec, scriptTag, artNum, numArts,
  752.         fileKind);
  753.     if (err != noErr) goto exit;
  754.     
  755.     if (*fileKind == kNoAttachedFile || *fileKind == kArtNotOnServer) {
  756.         FSpDelete(&fSpec);
  757.         return noErr;
  758.     }
  759.     
  760.     err = RunDecodeHelperProgram(&fSpec, *fileKind);
  761.     if (err != noErr) return err;
  762.     
  763.     return noErr;
  764.  
  765. exit:
  766.  
  767.     FSpDelete(&fSpec);
  768.     return err;
  769. }
  770.  
  771.  
  772.  
  773. /*----------------------------------------------------------------------------
  774.     ExtractBinariesForArticleWindow 
  775.     
  776.     Extract binaries for an article window.
  777.             
  778.     Entry:    wind = pointer to article window.
  779.             modifiers = modifiers field from event record.
  780.     
  781.     Exit:    function result = error code.
  782. ----------------------------------------------------------------------------*/
  783.  
  784. static OSErr ExtractBinariesForArticleWindow (WindowPtr wind, short modifiers)
  785. {
  786.     TWindow **info;
  787.     OSErr err = noErr;
  788.     TAttachedFileKind fileKind;
  789.     DialogPtr dlg;
  790.     short item;
  791.     
  792.     info = (TWindow**)GetWRefCon(wind);
  793.     
  794.     if ((**info).parentWindow == nil) {
  795.         err = MyGetNewDialog(kTooStupidAlert, ok, 0, &dlg);
  796.         if (err != noErr) return err;
  797.         SysBeep(0);
  798.         MyModalDialog(dlg, gDialogFilterUPP, &item);
  799.         err = DoClose(dlg);
  800.         if (err != noErr) return err;
  801.         return userCanceledErr;
  802.     }
  803.         
  804.     err = CheckForMissingParts(wind);
  805.     if (err != noErr) return err;
  806.     
  807.     err = ExtractBinaries((**info).parentWindow, (**info).parentSubject, 
  808.         1, 1, modifiers, &fileKind);
  809.     if (err != noErr) return err;
  810.     
  811.     if (fileKind == kNoAttachedFile) {
  812.         NoteMessageNumber(kStrArtHasNoAttachedFile);
  813.         return userCanceledErr;
  814.     }
  815.     
  816.     if (fileKind == kArtNotOnServer) {
  817.         NoteMessageNumber(kStrArtNotOnServer);
  818.         return userCanceledErr;
  819.     }
  820.     
  821.     return noErr;
  822. }
  823.  
  824.  
  825.  
  826. /*----------------------------------------------------------------------------
  827.     ExtractBinariesDragAndDrop 
  828.     
  829.     Extract binaries for an article window and the end of a drag
  830.     and drop sequence.
  831.             
  832.     Entry:    wind = pointer to article window.
  833.             fSpec = pointer to file spec.
  834.     
  835.     Exit:    function result = error code.
  836. ----------------------------------------------------------------------------*/
  837.  
  838. static OSErr ExtractBinariesDragAndDrop (WindowPtr wind, FSSpec *fSpec)
  839. {
  840.     TWindow **info;
  841.     OSErr err = noErr;
  842.     TAttachedFileKind fileKind;
  843.     
  844.     info = (TWindow**)GetWRefCon(wind);
  845.     
  846.     err = CheckForMissingParts(wind);
  847.     if (err != noErr) goto exit;
  848.     
  849.     err = SaveBinariesToFile((**info).parentWindow, (**info).parentSubject, 
  850.         fSpec, smSystemScript, 1, 1, &fileKind);
  851.     if (err != noErr) goto exit;
  852.     
  853.     if (fileKind == kNoAttachedFile) {
  854.         NoteMessageNumber(kStrArtHasNoAttachedFile);
  855.         err = userCanceledErr;
  856.         goto exit;
  857.     }
  858.     
  859.     if (fileKind == kArtNotOnServer) {
  860.         NoteMessageNumber(kStrArtNotOnServer);
  861.         return userCanceledErr;
  862.     }
  863.     
  864.     err = RunDecodeHelperProgram(fSpec, fileKind);
  865.     if (err != noErr) return err;
  866.     
  867.     return noErr;
  868.  
  869. exit:
  870.  
  871.     FSpDelete(fSpec);
  872.     return err;
  873. }
  874.  
  875.  
  876.  
  877. /*----------------------------------------------------------------------------
  878.     CanExtractBinaries 
  879.     
  880.     Check to see if we can extract binaries from an article window (it has
  881.     a parent subject window, and it either has an attached binary file or is
  882.     part 0 of a mutiple-part posting).
  883.             
  884.     Entry:    wind = pointer to article window.
  885.     
  886.     Exit:    function result = true if can extract binaries from this window.
  887. ----------------------------------------------------------------------------*/
  888.  
  889. static Boolean CanExtractBinaries (WindowPtr wind)
  890. {
  891.     WindowPtr parent;
  892.     TWindow **info, **parentInfo;
  893.     TSubject **subjectArray;
  894.     
  895.     info = (TWindow**)GetWRefCon(wind);
  896.     if ((**info).attachedFile) return true;
  897.     parent = (**info).parentWindow;
  898.     if (parent == nil) return false;
  899.     parentInfo = (TWindow**)GetWRefCon(parent);
  900.     subjectArray = (**parentInfo).subjectArray;
  901.     return (*subjectArray)[(**info).parentSubject].partNum == 0;
  902. }
  903.  
  904.  
  905.  
  906. /*----------------------------------------------------------------------------
  907.     DragAttachedFileIconSendProc 
  908.     
  909.     The Drag Manager send proc for dragging the attached file icon.
  910.             
  911.     Entry:    theType = flavor type = 'SPEC'.
  912.             dragSendRefCon = pointer to file spec, with file name
  913.                 filled in.
  914.             theItemRef = item reference.
  915.             theDragRef = drag reference.
  916.     
  917.     Exit:    function result = error code.
  918.             file spec volume reference number and directory id filled in.
  919.             file created.
  920.             file spec sent to Finder as flavor data.
  921. ----------------------------------------------------------------------------*/
  922.  
  923. static pascal OSErr DragAttachedFileIconSendProc (FlavorType theType,
  924.     void *dragSendRefCon, ItemReference theItemRef, DragReference theDragRef)
  925. {
  926.     AEDesc dropLocation;
  927.     OSErr err = noErr;
  928.     FSSpec *fSpec;
  929.     
  930.     if (DragTargetWasTrash(theDragRef)) return userCanceledErr;
  931.     
  932.     MyICReadSharedPrefs(kICeditorHelper);
  933.     
  934.     fSpec = (FSSpec*)dragSendRefCon;
  935.  
  936.     err = GetDropLocation(theDragRef, &dropLocation);
  937.     if (err != noErr) return err;
  938.     
  939.     err = GetDropLocationDirectory(&dropLocation, &fSpec->vRefNum, &fSpec->parID);
  940.     if (err != noErr) goto exit;
  941.     
  942.     err = MakeFileNameUnique(fSpec, nil);
  943.     if (err != noErr) goto exit;
  944.  
  945.     err = FSpCreate(fSpec, gPrefs.savedArtCreator, 'TEXT', smSystemScript);
  946.     if (err != noErr) goto exit;
  947.     
  948.     err = SetDragItemFlavorData(theDragRef, theItemRef, 'SPEC', 
  949.         fSpec, sizeof(FSSpec), 0);
  950.  
  951. exit:
  952.  
  953.     AEDisposeDesc(&dropLocation);
  954.     return err;
  955. }
  956.  
  957.  
  958. /*----------------------------------------------------------------------------
  959.     DragAttachedFileIcon 
  960.     
  961.     Handle a mouse down event in the attached file icon of an article 
  962.     window when the Drag Manager is present.
  963.             
  964.     Entry:    wind = pointer to article window.
  965.             iconRect = pointer to icon rectangle in local coords.
  966.             modifiers = modifiers field from event record.
  967.             
  968.     Exit:    function result = error code.
  969. ----------------------------------------------------------------------------*/
  970.  
  971. static OSErr DragAttachedFileIcon (WindowPtr wind, Rect *iconRect, short modifiers)
  972. {
  973.     OSErr err = noErr;
  974.     DragReference dragRef;
  975.     Boolean haveDragRef = false;
  976.     PromiseHFSFlavor promise;
  977.     RgnHandle dragRgn = nil;
  978.     Rect globalIconRect;
  979.     FSSpec fSpec;
  980.     
  981.     PlotIconID(iconRect, 0, ttSelected, kFileIconID);
  982.     if (WaitMouseMoved(gCurEvent.where)) {
  983.         err = NewDrag(&dragRef);
  984.         if (err != noErr) goto exit;
  985.         MyICReadSharedPrefs(kICeditorHelper);
  986.         haveDragRef = true;
  987.         promise.fileType = 'TEXT';
  988.         promise.fileCreator = gPrefs.savedArtCreator;
  989.         promise.fdFlags = 0;
  990.         promise.promisedFlavor = 'SPEC';
  991.         err = AddDragItemFlavor(dragRef, 1, flavorTypePromiseHFS, &promise, 
  992.             sizeof(PromiseHFSFlavor), 0);
  993.         if (err != noErr) goto exit;
  994.         err = AddDragItemFlavor(dragRef, 1, 'SPEC', nil, 0, 0);
  995.         if (err != noErr) goto exit;
  996.         dragRgn = NewRgn();
  997.         globalIconRect = *iconRect;
  998.         LocalToGlobalRect(&globalIconRect);
  999.         err = IconIDToRgn(dragRgn, &globalIconRect, ttNone, kFileIconID);
  1000.         if (err != noErr) goto exit;
  1001.         OutlineRegion(dragRgn);
  1002.         GetPString(kStrTempFileName, fSpec.name);
  1003.         err = SetDragSendProc(dragRef, gDragAttachedFileIconSendProcUPP, &fSpec);
  1004.         if (err != noErr) goto exit;
  1005.         err = TrackDrag(dragRef, &gCurEvent, dragRgn);
  1006.         if (err != noErr) goto exit;
  1007.         DisposeRgn(dragRgn);
  1008.         DisposeDrag(dragRef);
  1009.         PlotIconID(iconRect, 0, ttNone, kFileIconID);
  1010.         err = ExtractBinariesDragAndDrop(wind, &fSpec);
  1011.         if (err != noErr) goto exit;
  1012.     } else {
  1013.         PlotIconID(iconRect, 0, ttNone, kFileIconID);
  1014.          return ExtractBinariesForArticleWindow(wind, modifiers);
  1015.     }
  1016.     return noErr;
  1017.     
  1018. exit:
  1019.  
  1020.     if (haveDragRef) DisposeDrag(dragRef);
  1021.     if (dragRgn != nil) DisposeRgn(dragRgn);
  1022.     PlotIconID(iconRect, 0, ttNone, kFileIconID);
  1023.     return err;
  1024. }
  1025.  
  1026.  
  1027.  
  1028. /*----------------------------------------------------------------------------
  1029.     Find 
  1030.     
  1031.     Search an article window for a pattern.
  1032.             
  1033.     Entry:    wind = pointer to article window.
  1034.             offset = offset into currently displayed text section 
  1035.                 to begin search, or -1 to start search at beginning.
  1036.             gFindPattern = pattern.
  1037.     
  1038.     Exit:    function result = error code.
  1039. ----------------------------------------------------------------------------*/
  1040.  
  1041. static OSErr Find (WindowPtr wind, long offset)
  1042. {
  1043.     TWindow **info;
  1044.     Handle fullText;
  1045.     TEHandle theTE;
  1046.     short curSection, numSections, newSection;
  1047.     long **breaks;
  1048.     char state;
  1049.     long matchOffset, len;
  1050.     OSErr err = noErr;
  1051.     
  1052.     info = (TWindow**)GetWRefCon(wind);
  1053.     fullText = (**info).fullText;
  1054.     curSection = (**info).curSection;
  1055.     numSections = (**info).numSections;
  1056.     breaks = (**info).sectionBreaks;
  1057.     
  1058.     if (offset == -1) {
  1059.          offset = 0;
  1060.          if (!(**info).showDetails) offset += FindBody(fullText);
  1061.     } else {
  1062.         if (curSection == 0 && !(**info).showDetails)
  1063.             offset += FindBody(fullText);
  1064.         offset += (*breaks)[curSection];
  1065.     }
  1066.     len = (*breaks)[numSections] - offset;
  1067.     
  1068.     state = MyHGetState(fullText);
  1069.     MyHLock(fullText);
  1070.     err = MyNSubstringSearch(*fullText + offset, gFindPattern, len,
  1071.         &matchOffset, GiveTime);
  1072.     MyHSetState(fullText, state);
  1073.     if (err != noErr) return err;
  1074.     if (matchOffset == -1) {
  1075.         SysBeep(0);
  1076.     } else {
  1077.         offset += matchOffset;
  1078.         newSection = 0;
  1079.         while (offset >= (*breaks)[newSection+1]) newSection++;
  1080.         offset -= (*breaks)[newSection];
  1081.         if (newSection != curSection) {
  1082.             SetControlValue((**info).hScroll, newSection);
  1083.             ScrollSection(wind, curSection - newSection);
  1084.         }
  1085.         if (newSection == 0 && !(**info).showDetails)
  1086.             offset -= FindBody(fullText);
  1087.         theTE = (**info).theTE;
  1088.         TESetSelect(offset, offset + strlen(gFindPattern), theTE);
  1089.         TEScrollScrollToMiddle(theTE, offset, (**info).vScroll);
  1090.     }
  1091.     return noErr;
  1092. }
  1093.  
  1094.  
  1095.  
  1096. /*----------------------------------------------------------------------------
  1097.     DoSave 
  1098.     
  1099.     Handle the "Save" command.
  1100.             
  1101.     Entry:    wind = pointer to article window.
  1102.             modifiers = modifiers field from event record.
  1103.     
  1104.     Exit:    function result = error code.
  1105. ----------------------------------------------------------------------------*/
  1106.  
  1107. static OSErr DoSave (WindowPtr wind, short modifiers)
  1108. {
  1109.     Str31 fileName;
  1110.     FSSpec fSpec;
  1111.     ScriptCode scriptTag;
  1112.     Boolean append = false;
  1113.     OSErr err = noErr;
  1114.     TWindow **info;
  1115.     Boolean saveEncodedText, saveThreadsToSeparateFiles;
  1116.  
  1117.     info = (TWindow**)GetWRefCon(wind);
  1118.     saveEncodedText = gPrefs.saveEncodedText;
  1119.     saveThreadsToSeparateFiles = gPrefs.saveThreadsToSeparateFiles;
  1120.     FormFileName(wind, fileName);
  1121.     if (gPrefs.savedArtDefaultFolder && (modifiers & optionKey) == 0) {
  1122.         err = CheckArticleFileExists(fileName, &fSpec, &scriptTag, &append,
  1123.             &saveEncodedText, &saveThreadsToSeparateFiles);
  1124.         if (err != noErr) return err;
  1125.     } else {
  1126.         err = PresentStandardArticleSaveFileDialog(fileName, &fSpec, &scriptTag, 
  1127.             &saveEncodedText, &saveThreadsToSeparateFiles);
  1128.         if (err != noErr) return err;
  1129.     }
  1130.     return SaveArticleWindToFile(wind, modifiers, &fSpec, scriptTag, append,
  1131.         saveEncodedText);
  1132. }
  1133.  
  1134.  
  1135.  
  1136. /*----------------------------------------------------------------------------
  1137.     DoSaveAs
  1138.     
  1139.     Handle the "Save As" command.
  1140.             
  1141.     Entry:    wind = pointer to article window.
  1142.             modifiers = modifiers field from event record.
  1143.     
  1144.     Exit:    function result = error code.
  1145. ----------------------------------------------------------------------------*/
  1146.  
  1147. static OSErr DoSaveAs (WindowPtr wind, short modifiers)
  1148. {
  1149.     Str31 fileName;
  1150.     FSSpec fSpec;
  1151.     ScriptCode scriptTag;
  1152.     OSErr err = noErr;
  1153.     TWindow **info;
  1154.     Boolean saveEncodedText, saveThreadsToSeparateFiles;
  1155.  
  1156.     info = (TWindow**)GetWRefCon(wind);
  1157.     saveEncodedText = gPrefs.saveEncodedText;
  1158.     saveThreadsToSeparateFiles = gPrefs.saveThreadsToSeparateFiles;
  1159.     FormFileName(wind, fileName);
  1160.     err = PresentStandardArticleSaveFileDialog(fileName, &fSpec, 
  1161.         &scriptTag, &saveEncodedText, &saveThreadsToSeparateFiles);
  1162.     if (err != noErr) return err;
  1163.     return SaveArticleWindToFile(wind, modifiers, &fSpec, scriptTag, false,
  1164.         saveEncodedText);
  1165. }
  1166.  
  1167.  
  1168.  
  1169. /*----------------------------------------------------------------------------
  1170.     DoPrint 
  1171.     
  1172.     Handle the "Print" command.
  1173.             
  1174.     Entry:    wind = pointer to article window.
  1175.             modifiers = modifiers field from event record.
  1176.             
  1177.     Exit:    function result = error code.
  1178. ----------------------------------------------------------------------------*/
  1179.  
  1180. static OSErr DoPrint (WindowPtr wind, short modifiers)
  1181. {
  1182.     TWindow **info;
  1183.     TEHandle theTE;
  1184.     Handle text, fullText;
  1185.     CStr255 subject, from, str;
  1186.     OSErr err = noErr;
  1187.     long start, end;
  1188.     
  1189.     info = (TWindow**)GetWRefCon(wind);
  1190.     fullText = (**info).fullText;
  1191.     theTE = (**info).theTE;
  1192.     start = (**theTE).selStart;
  1193.     end = (**theTE).selEnd;
  1194.     
  1195.     err = StartPrint();
  1196.     if (err != noErr) return err;
  1197.     
  1198.     err = DisplayStatusMessageNumber(kStrPrinting);
  1199.     if (err != noErr) return err;
  1200.     
  1201.     FindHeaderCString(fullText, "Subject", subject, sizeof(subject));
  1202.     if (FindHeaderCString(fullText, "From", from, sizeof(from))) {
  1203.         FormatAuthorName(from);
  1204.         sprintf(str, "%.40s, %.100s", from, subject);
  1205.     } else {
  1206.         strcpy(str, subject);
  1207.     }
  1208.  
  1209.     if ((modifiers & shiftKey) == 0 || start >= end) { 
  1210.         text = fullText;
  1211.         start = 0;
  1212.         end = MyGetHandleSize(text);
  1213.     } else {
  1214.         text = (**theTE).hText;
  1215.     }
  1216.     
  1217.     return PrintText(text, start, end, str);
  1218. }
  1219.  
  1220.  
  1221.  
  1222. /*----------------------------------------------------------------------------
  1223.     DoAppend 
  1224.     
  1225.     Handle the "Append" command.
  1226.             
  1227.     Entry:    wind = pointer to article window.
  1228.             modifiers = modifiers field from event record.
  1229.     
  1230.     Exit:    function result = error code.
  1231. ----------------------------------------------------------------------------*/
  1232.  
  1233. static OSErr DoAppend (WindowPtr wind, short modifiers)
  1234. {
  1235.     FSSpec fSpec;
  1236.     OSErr err = noErr;
  1237.     TWindow **info;
  1238.     Boolean saveEncodedText;
  1239.     
  1240.     info = (TWindow**)GetWRefCon(wind);
  1241.     saveEncodedText = gPrefs.saveEncodedText;
  1242.     err = PresentStandardArticleGetFileDialog(&fSpec, &saveEncodedText);
  1243.     if (err != noErr) return err;
  1244.     return SaveArticleWindToFile(wind, modifiers, &fSpec, smSystemScript, true,
  1245.         saveEncodedText);
  1246. }
  1247.  
  1248.  
  1249.  
  1250. /*----------------------------------------------------------------------------
  1251.     DoCopy 
  1252.     
  1253.     Handle the "Copy" command.
  1254.             
  1255.     Entry:    wind = pointer to article window.
  1256. ----------------------------------------------------------------------------*/
  1257.  
  1258. static void DoCopy (WindowPtr wind)
  1259. {
  1260.     TWindow **info;
  1261.     TEHandle theTE;
  1262.     
  1263.     info = (TWindow**)GetWRefCon(wind);
  1264.     theTE = (**info).theTE;
  1265.     MyTECopy(theTE);
  1266. }
  1267.  
  1268.  
  1269.  
  1270. /*----------------------------------------------------------------------------
  1271.     DoSelectAll 
  1272.     
  1273.     Handle the "Select All" command.
  1274.             
  1275.     Entry:    wind = pointer to article window.
  1276. ----------------------------------------------------------------------------*/
  1277.  
  1278. static void DoSelectAll (WindowPtr wind)
  1279. {
  1280.     TWindow **info;
  1281.     TEHandle theTE;
  1282.     
  1283.     info = (TWindow**)GetWRefCon(wind);
  1284.     theTE = (**info).theTE;
  1285.     TESetSelect(0, 0x7fff, theTE);
  1286. }
  1287.  
  1288.  
  1289.  
  1290. /*----------------------------------------------------------------------------
  1291.     DoFind 
  1292.     
  1293.     Handle the "Find" command for an article window.
  1294.             
  1295.     Entry:    wind = pointer to article window.
  1296.     
  1297.     Exit:    function result = error code.
  1298. ----------------------------------------------------------------------------*/
  1299.  
  1300. static OSErr DoFind (WindowPtr wind)
  1301. {
  1302.     TWindow **info;
  1303.     OSErr err = noErr;
  1304.     TEHandle theTE;
  1305.     
  1306.     err = DoFindDialog();
  1307.     if (err != noErr) return err;
  1308.     info = (TWindow**)GetWRefCon(wind);
  1309.     theTE = (**info).theTE;
  1310.     return Find(wind, gPrefs.startFindAtBeginning ? -1 : (**theTE).selStart);
  1311. }
  1312.  
  1313.  
  1314.  
  1315. /*----------------------------------------------------------------------------
  1316.     DoFindAgain
  1317.     
  1318.     Handle the "Find Again" command for an article window.
  1319.             
  1320.     Entry:    wind = pointer to article window.
  1321.     
  1322.     Exit:    function result = error code.
  1323. ----------------------------------------------------------------------------*/
  1324.  
  1325. static OSErr DoFindAgain (WindowPtr wind)
  1326. {
  1327.     TWindow **info;
  1328.     TEHandle theTE;
  1329.     
  1330.     info = (TWindow**)GetWRefCon(wind);
  1331.     theTE = (**info).theTE;
  1332.     return Find(wind, (**theTE).selEnd);
  1333. }
  1334.  
  1335.  
  1336.  
  1337. /*----------------------------------------------------------------------------
  1338.     DoEnterSelection
  1339.     
  1340.     Handle the "Enter Selection" command for an article window.
  1341.             
  1342.     Entry:    wind = pointer to article window.
  1343.     
  1344.     Exit:    function result = error code.
  1345. ----------------------------------------------------------------------------*/
  1346.  
  1347. static OSErr DoEnterSelection (WindowPtr wind)
  1348. {
  1349.     TWindow **info;
  1350.     TEHandle theTE;
  1351.     short selStart, selEnd, len;
  1352.     Handle hText;
  1353.     
  1354.     info = (TWindow**)GetWRefCon(wind);
  1355.     theTE = (**info).theTE;
  1356.     selStart = (**theTE).selStart;
  1357.     selEnd = (**theTE).selEnd;
  1358.     hText = (**theTE).hText;
  1359.     if (selStart >= selEnd || selEnd > selStart + 255) return noErr;
  1360.     len = selEnd - selStart;
  1361.     BlockMoveData(*hText + selStart, gFindPattern, len);
  1362.     gFindPattern[len] = 0;
  1363.     return noErr;
  1364. }
  1365.  
  1366.  
  1367.  
  1368. /*----------------------------------------------------------------------------
  1369.     DoShowHideDetails 
  1370.     
  1371.     Handle the "Show/Hide Details" command.
  1372.             
  1373.     Entry:    wind = pointer to article window.
  1374.     
  1375.     Exit:    function result = error code.
  1376. ----------------------------------------------------------------------------*/
  1377.  
  1378. static OSErr DoShowHideDetails (WindowPtr wind)
  1379. {
  1380.     TWindow **info;
  1381.     Boolean showDetails;
  1382.     short curSection;
  1383.     TEHandle theTE;
  1384.     Handle text;
  1385.     long **breaks;
  1386.     long length, offset;
  1387.     Rect textRect;
  1388.     OSErr err = noErr;
  1389.     char state;
  1390.     
  1391.     info = (TWindow**)GetWRefCon(wind);
  1392.     theTE = (**info).theTE;
  1393.     showDetails = (**info).showDetails = !(**info).showDetails;
  1394.     SetEditMenuShowHideDetails(!showDetails);
  1395.     curSection = (**info).curSection;
  1396.     breaks = (**info).sectionBreaks;
  1397.     GetTextRect(wind, &textRect);
  1398.     
  1399.     if (curSection != 0) ScrollActionSection((**info).hScroll, kScrollToHome);
  1400.     TEScrollScrollByPartCode(theTE, (**info).vScroll, kScrollToHome);
  1401.     
  1402.     text = (**info).fullText;
  1403.     offset = 0;
  1404.     length = (*breaks)[1];
  1405.     if (!showDetails) {
  1406.         offset = FindBody(text);
  1407.         if (offset > length) offset = length;
  1408.         length -= offset;
  1409.     }
  1410.     state = MyHGetState(text);
  1411.     MyHLock(text);
  1412.     err = MyTESetText(*text + offset, length, theTE);
  1413.     MyHSetState(text, state);
  1414.     if (err != noErr) return err;
  1415.     TESetSelect(0, 0, theTE);
  1416.     TEScrollAdjustScrollMax(theTE, (**info).vScroll);
  1417.     InvalRect(&textRect);
  1418.  
  1419.     if (showDetails && gPrefs.reZoomWindows && 
  1420.         GetControlMaximum((**info).vScroll) > 0 && !(**info).windPosLocked) 
  1421.     {
  1422.         err = DoZoom(wind, inZoomOut);
  1423.         if (err != noErr) return err;
  1424.     } else {
  1425.         SetWindowNeedsZooming(wind);
  1426.     }
  1427.     return noErr;
  1428. }
  1429.  
  1430.  
  1431.  
  1432. /*----------------------------------------------------------------------------
  1433.     DoRot13 
  1434.     
  1435.     Handle the "Rot13" command.
  1436.             
  1437.     Entry:    wind = pointer to article window.
  1438. ----------------------------------------------------------------------------*/
  1439.  
  1440. static void DoRot13 (WindowPtr wind)
  1441. {
  1442.     TWindow **info;
  1443.     TEHandle theTE;
  1444.     Rect r;
  1445.     short selStart, selEnd, curSection;
  1446.     Boolean showDetails;
  1447.     Handle hText, text;
  1448.     long **breaks;
  1449.     long offset;
  1450.     
  1451.     info = (TWindow**)GetWRefCon(wind);
  1452.     showDetails = (**info).showDetails;
  1453.     theTE = (**info).theTE;
  1454.     curSection = (**info).curSection;
  1455.     text = (**info).fullText;
  1456.     breaks = (**info).sectionBreaks;
  1457.     selStart = (**theTE).selStart;
  1458.     selEnd = (**theTE).selEnd;
  1459.     if (selStart >= selEnd) {
  1460.         selStart = 0;
  1461.         selEnd = (**theTE).teLength;
  1462.     }
  1463.     hText = (**theTE).hText;
  1464.     offset = curSection == 0 ? 0 : (*breaks)[curSection];
  1465.     
  1466.     Rot13Text(hText, selStart, selEnd);
  1467.     r = (**theTE).viewRect;
  1468.     InvalRect(&r);
  1469.     
  1470.     if (curSection == 0 && !showDetails) offset += FindBody(text);
  1471.     BlockMoveData(*hText + selStart, *text + offset + selStart, selEnd - selStart);
  1472. }
  1473.  
  1474.  
  1475.  
  1476. /*----------------------------------------------------------------------------
  1477.     DoReply 
  1478.     
  1479.     Handle the "Reply" command.
  1480.             
  1481.     Entry:    wind = pointer to article window.
  1482.             modifiers = modifiers field from event record.
  1483.             
  1484.     Exit:    function result = error code.
  1485. ----------------------------------------------------------------------------*/
  1486.  
  1487. static OSErr DoReply (WindowPtr wind, short modifiers)
  1488. {
  1489.     TWindow **info;
  1490.     Handle text, quoteText;
  1491.     long start, end;
  1492.     TEHandle theTE;
  1493.         
  1494.     info = (TWindow**)GetWRefCon(wind);
  1495.     text = (**info).fullText;
  1496.     
  1497.     if ((modifiers & shiftKey) == 0) {
  1498.         quoteText = nil;
  1499.     } else {
  1500.         theTE = (**info).theTE;
  1501.         quoteText = (**theTE).hText;
  1502.         start = (**theTE).selStart;
  1503.         end = (**theTE).selEnd;
  1504.     }
  1505.     
  1506.     return OpenReplyWindow(text, quoteText, start, end, (modifiers & optionKey) != 0);
  1507. }
  1508.  
  1509.  
  1510.  
  1511. /*----------------------------------------------------------------------------
  1512.     DoForward 
  1513.     
  1514.     Handle the "Forward" command.
  1515.             
  1516.     Entry:    wind = pointer to article window.
  1517.             modifiers = modifiers field from event record.
  1518.             
  1519.     Exit:    function result = error code.
  1520. ----------------------------------------------------------------------------*/
  1521.  
  1522. static OSErr DoForward (WindowPtr wind, short modifiers)
  1523. {
  1524.     TWindow **info;
  1525.     Handle text, quoteText;
  1526.     long start, end;
  1527.     TEHandle theTE;
  1528.         
  1529.     info = (TWindow**)GetWRefCon(wind);
  1530.     text = (**info).fullText;
  1531.     
  1532.     if ((modifiers & shiftKey) == 0) {
  1533.         quoteText = nil;
  1534.     } else {
  1535.         theTE = (**info).theTE;
  1536.         quoteText = (**theTE).hText;
  1537.         start = (**theTE).selStart;
  1538.         end = (**theTE).selEnd;
  1539.     }
  1540.     
  1541.     return OpenForwardWindow(text, quoteText, start, end, (modifiers & optionKey) != 0);
  1542. }
  1543.  
  1544.  
  1545.  
  1546. /*----------------------------------------------------------------------------
  1547.     DoRedirect 
  1548.     
  1549.     Handle the "Redirect" command.
  1550.             
  1551.     Entry:    wind = pointer to article window.
  1552.             modifiers = modifiers field from event record.
  1553.             
  1554.     Exit:    function result = error code.
  1555. ----------------------------------------------------------------------------*/
  1556.  
  1557. static OSErr DoRedirect (WindowPtr wind, short modifiers)
  1558. {
  1559.     TWindow **info;
  1560.     Handle text, quoteText;
  1561.     long start, end;
  1562.     TEHandle theTE;
  1563.         
  1564.     info = (TWindow**)GetWRefCon(wind);
  1565.     text = (**info).fullText;
  1566.     
  1567.     if ((modifiers & shiftKey) == 0) {
  1568.         quoteText = nil;
  1569.     } else {
  1570.         theTE = (**info).theTE;
  1571.         quoteText = (**theTE).hText;
  1572.         start = (**theTE).selStart;
  1573.         end = (**theTE).selEnd;
  1574.     }
  1575.     
  1576.     return OpenRedirectWindow(text, quoteText, start, end, (modifiers & optionKey) != 0);
  1577. }
  1578.  
  1579.  
  1580.  
  1581. /*----------------------------------------------------------------------------
  1582.     DoExtractBinaries
  1583.     
  1584.     Handle the "Extract Binaries" command.
  1585.     
  1586.     Entry:    wind = pointer to article window.
  1587.             modifiers = modifiers field from event record.
  1588.     
  1589.     Exit:    function result = error code.
  1590. ----------------------------------------------------------------------------*/
  1591.  
  1592. static OSErr DoExtractBinaries (WindowPtr wind, short modifiers)
  1593. {
  1594.     return ExtractBinariesForArticleWindow(wind, modifiers);
  1595. }
  1596.  
  1597.  
  1598.  
  1599. /*----------------------------------------------------------------------------
  1600.     DoOpenAllReferences
  1601.     
  1602.     Handle the "Open all References" command.
  1603.     
  1604.     Entry:    wind = pointer to article window.
  1605.     
  1606.     Exit:    function result = error code.
  1607. ----------------------------------------------------------------------------*/
  1608.  
  1609. static OSErr DoOpenAllReferences (WindowPtr wind)
  1610. {
  1611.     WindowPtr newWind;
  1612.     TWindow **info;
  1613.     Handle fullText, references = nil;
  1614.     CStr255 msgId;
  1615.     short totalRefs = 0, totalOpenedRefs = 0;
  1616.     char *p, *q;
  1617.     long len;
  1618.     OSErr err = noErr;
  1619.     
  1620.     info = (TWindow**)GetWRefCon(wind);
  1621.     fullText = (**info).fullText;
  1622.     
  1623.     err = FindHeaderHandle(fullText, "References", &references);
  1624.     if (err != noErr) goto exit;
  1625.     if (references == nil) {
  1626.         NoteMessageNumber(kStrNoRefs);
  1627.         return noErr;
  1628.     }
  1629.  
  1630.     MyHLock(references);
  1631.     q = *references + MyGetHandleSize(references) - 1;
  1632.     while (q >= *references) {
  1633.         while (*q != '>' && q >= *references) q--;
  1634.         p = q-1;
  1635.         while (*p != '<' && p >= *references) p--;
  1636.         len = q - p + 1;
  1637.         q = p - 1;
  1638.         if (len <= 2 || len > 255) continue;
  1639.         BlockMoveData(p, msgId, len);
  1640.         *(msgId + len) = 0;
  1641.         totalRefs++;
  1642.         if (CheckAlreadyOpen(msgId)) {
  1643.             totalOpenedRefs++;
  1644.         } else {
  1645.             err = GetArticleAndMakeNewWindow(nil, 0, nil, 0, msgId, nil, false, nil, true, 
  1646.                 &newWind);
  1647.             if (err != noErr) goto exit;
  1648.             if (newWind != nil) totalOpenedRefs++;
  1649.         }
  1650.     }
  1651.     MyDisposeHandle(references);
  1652.  
  1653.     if (totalOpenedRefs < totalRefs) {
  1654.         if (totalOpenedRefs == 0) {
  1655.             NoteMessageNumber(kStrNoneOpened);
  1656.         } else {
  1657.             NoteMessageNumber(kStrSomeNotOpened);
  1658.         }
  1659.     }
  1660.     return noErr;
  1661.  
  1662. exit:
  1663.  
  1664.     MyDisposeHandle(references);
  1665.     return err;
  1666. }
  1667.  
  1668.  
  1669.  
  1670. /*----------------------------------------------------------------------------
  1671.     DoCancelArticle 
  1672.     
  1673.     Handles the "Cancel Article" command.
  1674.     
  1675.     Entry:    wind = pointer to article window.
  1676.     
  1677.     Exit:    function result = error code.
  1678. ----------------------------------------------------------------------------*/
  1679.  
  1680. static OSErr DoCancelArticle (WindowPtr wind)
  1681. {
  1682.     TWindow **info;
  1683.     Handle text;
  1684.     Handle groups = nil;
  1685.     CStr255 idStr, statusMsg;
  1686.     OSErr err = noErr;
  1687.     
  1688.     info = (TWindow**)GetWRefCon(wind);
  1689.     text = (**info).fullText;
  1690.     if (!UserIsPoster(text)) goto exit1;
  1691.  
  1692.     GetCString(kStrCancelingStatusMsg, statusMsg);
  1693.     err = DisplayStatusMessage(statusMsg);
  1694.     if (err != noErr) return err;
  1695.     
  1696.     if (!FindHeaderCString(text, "Message-ID", idStr, sizeof(idStr))) goto exit2;
  1697.     
  1698.     err = FindHeaderHandle(text, "Newsgroups", &groups);
  1699.     if (err != noErr) goto exit3;
  1700.     if (groups == nil) goto exit4;
  1701.     
  1702.     err = CancelArticle(idStr, groups, statusMsg);
  1703.     if (err != noErr) goto exit3;
  1704.     
  1705.     MyDisposeHandle(groups);
  1706.     return noErr;
  1707.     
  1708. exit1:
  1709.  
  1710.     MyDisposeHandle(groups);
  1711.     ErrorMessageNumber(kStrNotCanceled);
  1712.     return userCanceledErr;
  1713.     
  1714. exit2:
  1715.  
  1716.     MyDisposeHandle(groups);
  1717.     ErrorMessageNumber(kStrNoMsgID);
  1718.     return userCanceledErr;
  1719.     
  1720. exit3:
  1721.  
  1722.     MyDisposeHandle(groups);
  1723.     return err;
  1724.     
  1725. exit4:
  1726.  
  1727.     MyDisposeHandle(groups);
  1728.     ErrorMessageNumber(kStrNoNewsgroupsHeader);
  1729.     return userCanceledErr;
  1730. }
  1731.  
  1732.  
  1733.  
  1734.  
  1735. /*----------------------------------------------------------------------------
  1736.     Activate 
  1737.     
  1738.     Handle an activate event for an article window.
  1739.             
  1740.     Entry:    wind = pointer to article window.
  1741.             act = true to activate, false to deactivate
  1742. ----------------------------------------------------------------------------*/
  1743.  
  1744. static void Activate (WindowPtr wind, Boolean act)
  1745. {
  1746.     TWindow **info;
  1747.     TEHandle theTE;
  1748.     ControlHandle vScroll, hScroll;
  1749.     Rect r;
  1750.  
  1751.     info = (TWindow**)GetWRefCon(wind);
  1752.     theTE = (**info).theTE;
  1753.     vScroll = (**info).vScroll;
  1754.     hScroll = (**info).hScroll;
  1755.     if (act) {
  1756.         ShowControl(vScroll);
  1757.         if (hScroll != nil) ShowControl(hScroll);
  1758.         TEActivate(theTE);
  1759.     } else {
  1760.         HideControl(vScroll);
  1761.         if (hScroll != nil) HideControl(hScroll);
  1762.         TEDeactivate(theTE);
  1763.     }
  1764.     r = wind->portRect;
  1765.     r.top = r.bottom - 15;
  1766.     r.left = r.right - 15;
  1767.     InvalRect(&r);
  1768. }
  1769.  
  1770.  
  1771.  
  1772. /*----------------------------------------------------------------------------
  1773.     Update 
  1774.     
  1775.     Handle an update event for an article window.
  1776.             
  1777.     Entry:    wind = pointer to article window.
  1778. ----------------------------------------------------------------------------*/
  1779.  
  1780. static void Update (WindowPtr wind)
  1781. {
  1782.     TWindow **info, **parentInfo;
  1783.     short panelHeight, windWidth, lineHeight;
  1784.     Rect r;
  1785.     TEHandle theTE;
  1786.     WindowPtr parent;
  1787.     TSubject subject, **subjectArray;
  1788.     Handle fullText;
  1789.     CStr255 from, organization, date, newsgroups;
  1790.     CStr255 articleXofYFormat;
  1791.     char msg[600];
  1792.     FontInfo fontInfo;
  1793.     short len, threadInfoWidth, v;
  1794.     char c;
  1795.     Rect iconRect;
  1796.     short iconSlop = 0;
  1797.  
  1798.     info = (TWindow**)GetWRefCon(wind);
  1799.     panelHeight = (**info).panelHeight;
  1800.     theTE = (**info).theTE;
  1801.     fullText = (**info).fullText;
  1802.     GetFontInfo(&fontInfo);
  1803.     lineHeight = fontInfo.leading + fontInfo.ascent + fontInfo.descent;
  1804.     if (lineHeight < 11) lineHeight = 11;
  1805.     
  1806.     r = wind->portRect;
  1807.     r.top += panelHeight;
  1808.     ClipRect(&r);
  1809.     DrawGrowIcon(wind);
  1810.     ClipRect(&wind->portRect);
  1811.     
  1812.     UpdateControls(wind, wind->visRgn);
  1813.     
  1814.     windWidth = wind->portRect.right - wind->portRect.left;
  1815.     MoveTo(0, panelHeight-3);
  1816.     LineTo(windWidth, panelHeight-3);
  1817.     MoveTo(0, panelHeight-1);
  1818.     LineTo(windWidth, panelHeight-1);
  1819.     
  1820.     if (CanExtractBinaries(wind)) {
  1821.         SetRect(&iconRect, windWidth-32, 0, windWidth, 32);
  1822.         PlotIconID(&iconRect, 0, ttNone, kFileIconID);
  1823.         iconSlop = 32;
  1824.     }
  1825.         
  1826.     FindHeaderCString(fullText, "From", from, sizeof(from));
  1827.     GetCString(kStrFrom, msg);
  1828.     strcat(msg, from);
  1829.     v = fontInfo.ascent + 3;
  1830.     DrawPanelString(msg, v, windWidth-20-iconSlop);
  1831.     
  1832.     FindHeaderCString(fullText, "Organization", organization, sizeof(organization));
  1833.     GetCString(kStrOrg, msg);
  1834.     strcat(msg, organization);
  1835.     v += lineHeight;
  1836.     DrawPanelString(msg, v, windWidth-20-iconSlop);
  1837.     
  1838.     FindHeaderCString(fullText, "Date", date, sizeof(date));
  1839.     Cleanup822Date(date);
  1840.     GetCString(kStrDate, msg);
  1841.     strcat(msg, date);
  1842.     v += lineHeight;
  1843.     DrawPanelString(msg, v, windWidth-20-iconSlop);
  1844.  
  1845.     v += lineHeight;
  1846.     threadInfoWidth = 0;
  1847.     parent = (**info).parentWindow;
  1848.     if (parent != nil) {
  1849.         parentInfo = (TWindow**)GetWRefCon(parent);
  1850.         subjectArray = (**parentInfo).subjectArray;
  1851.         subject = (*subjectArray)[(**info).parentSubject];
  1852.         if (subject.threadLength > 1) {
  1853.             c = subject.threadOrdinal == subject.threadLength ? '•' : '…';
  1854.             GetCString(kStrArticleXofY, articleXofYFormat);
  1855.             sprintf(msg, articleXofYFormat, subject.threadOrdinal, subject.threadLength, c);
  1856.             len = strlen(msg);
  1857.             threadInfoWidth = TextWidth(msg, 0, len);
  1858.             MoveTo(windWidth - 10 - threadInfoWidth, v);
  1859.             DrawText(msg, 0, len);
  1860.         }
  1861.     }
  1862.         
  1863.     FindHeaderCString(fullText, "Newsgroups", newsgroups, sizeof(newsgroups));
  1864.     GetCString(kStrNewsgroups, msg);
  1865.     strcat(msg, newsgroups);
  1866.     DrawPanelString(msg, v, windWidth-30-threadInfoWidth);
  1867.     
  1868.     if ((**info).numSections > 1) DrawSectionMessage(wind);
  1869.     
  1870.     TEUpdate(&wind->portRect, theTE);
  1871.     
  1872.     DrawPadlockIcon(wind);
  1873. }
  1874.  
  1875.  
  1876.  
  1877. /*----------------------------------------------------------------------------
  1878.     Mouse 
  1879.     
  1880.     Handle a mouse down event in the content area of an article window.
  1881.             
  1882.     Entry:    wind = pointer to article window.
  1883.             where = location of mouse down in local coords.
  1884.             modifiers = modifiers field from event record.
  1885.             
  1886.     Exit:    function result = error code.
  1887. ----------------------------------------------------------------------------*/
  1888.  
  1889. static OSErr Mouse (WindowPtr wind, Point where, short modifiers)
  1890. {
  1891.     TWindow **info;
  1892.     TEHandle theTE;
  1893.     Rect viewRect, iconRect, triangleRect, hotRect;
  1894.     short part, oldVal, dv,dh;
  1895.     ControlHandle control, vScroll, hScroll;
  1896.     OSErr err = noErr;
  1897.     Boolean dragged, trashed;
  1898.     short oldSelStart, oldSelEnd, height;
  1899.     
  1900.     if (HandlePadlockClick(wind, where, &gPrefs.articleWindPosLocked, 
  1901.         &gPrefs.articleWindLocn)) return noErr;
  1902.  
  1903.     info = (TWindow**) GetWRefCon(wind);
  1904.     theTE = (**info).theTE;
  1905.     vScroll = (**info).vScroll;
  1906.     hScroll = (**info).hScroll;
  1907.     
  1908.     if (hScroll != nil) {
  1909.         height = wind->portRect.bottom;
  1910.         SetRect(&triangleRect, kSectionMargin - 30, wind->portRect.bottom - 17,
  1911.             kSectionMargin - 14, wind->portRect.bottom - 1);
  1912.         hotRect = triangleRect;
  1913.         hotRect.top += 2;
  1914.         hotRect.right -= 1;
  1915.         if (TrackIconClick(where, &triangleRect, &hotRect, kLeftSectionArrowID)) {
  1916.             ScrollActionSection(hScroll, inUpButton);
  1917.             return noErr;
  1918.         }
  1919.         OffsetRect(&triangleRect, 15, 0);
  1920.         OffsetRect(&hotRect, 15, 0);
  1921.         if (TrackIconClick(where, &triangleRect, &hotRect, kRightSectionArrowID)) {
  1922.             ScrollActionSection(hScroll, inDownButton);
  1923.             return noErr;
  1924.         }
  1925.     }
  1926.     
  1927.     viewRect = (**theTE).viewRect;
  1928.     InsetRect(&viewRect, -kTextMargin, 0);
  1929.     SetRect(&iconRect, wind->portRect.right - 32, 0, wind->portRect.right, 32);
  1930.     part = FindControl(where, wind, &control);
  1931.     if (part != 0) {
  1932.         if (control == vScroll) {
  1933.             if (part == inThumb) {
  1934.                 oldVal = GetControlValue(vScroll);
  1935.                 TrackControl(vScroll, where, nil);
  1936.                 dv = GetControlValue(vScroll) - oldVal;
  1937.                 if (dv != 0) TEScrollScrollText(theTE, vScroll, -dv);
  1938.             } else {
  1939.                 SetControlReference(vScroll, 0);
  1940.                 TrackControl(vScroll, where, gScrollActionUPP);
  1941.                 SetControlReference(vScroll, 1);
  1942.                 TEScrollAdjustScrollMax(theTE, vScroll);
  1943.             }
  1944.         } else if (control == hScroll) {
  1945.             if (part == inThumb) {
  1946.                 oldVal = GetControlValue(hScroll);
  1947.                 TrackControl(hScroll, where, nil);
  1948.                 dh = GetControlValue(hScroll) - oldVal;
  1949.                 if (dh != 0) ScrollSection(wind, -dh);
  1950.             } else {
  1951.                 TrackControl(hScroll, where, gScrollActionSectionUPP);
  1952.             }
  1953.         }
  1954.     } else if (PtInRect(where, &viewRect)) {
  1955.         err = DragText(&gCurEvent, where, theTE, &dragged, &trashed);
  1956.         if (err != noErr) return err;
  1957.         if (wind == FrontWindow() && !dragged) {
  1958.             oldSelStart = (**theTE).selStart;
  1959.             oldSelEnd = (**theTE).selEnd;
  1960.             TEClick(where, (modifiers & shiftKey) != 0, theTE);
  1961.             err = CommandClick(wind, theTE, oldSelStart, oldSelEnd, modifiers);
  1962.             if (err != noErr) return err;
  1963.         }
  1964.      } else if (CanExtractBinaries(wind) && PtInRect(where, &iconRect)) {
  1965.          if (gHaveDragMgr) {
  1966.              return DragAttachedFileIcon(wind, &iconRect, modifiers);
  1967.          } else if (TrackIconClick(where, &iconRect, nil, kFileIconID)) {
  1968.              return ExtractBinariesForArticleWindow(wind, modifiers);
  1969.          }
  1970.      }
  1971.      
  1972.      return noErr;
  1973. }
  1974.  
  1975.  
  1976.  
  1977. /*----------------------------------------------------------------------------
  1978.     Draggable
  1979.     
  1980.     Determine whether a mouse down event is on a draggable object in an 
  1981.     article window.
  1982.     
  1983.     Entry:    wind = pointer to article window.
  1984.             where = location of mouse down event, in local coordinates.
  1985.             modifiers = modifiers field from event record.
  1986.             
  1987.     Exit:    function result = true if object is draggable.
  1988. ----------------------------------------------------------------------------*/
  1989.  
  1990. static Boolean Draggable (WindowPtr wind, Point where, short modifiers)
  1991. {
  1992.     Rect iconRect;
  1993.     RgnHandle iconRgn;
  1994.     OSErr err = noErr;
  1995.     Boolean result;
  1996.     short windWidth;
  1997.     TWindow **info;
  1998.     TEHandle theTE;
  1999.  
  2000.     if (CanExtractBinaries(wind)) {
  2001.         windWidth = wind->portRect.right;
  2002.         SetRect(&iconRect, windWidth-32, 0, windWidth, 32);
  2003.         iconRgn = NewRgn();
  2004.         err = IconIDToRgn(iconRgn, &iconRect, ttNone, kFileIconID);
  2005.         result = err == noErr && PtInRgn(where, iconRgn);
  2006.         DisposeRgn(iconRgn);
  2007.         if (result) return true;
  2008.     }
  2009.     info = (TWindow**)GetWRefCon(wind);
  2010.     theTE = (**info).theTE;
  2011.     return PtInTEHiliteRgn(where, theTE);
  2012. }
  2013.  
  2014.  
  2015.  
  2016. /*----------------------------------------------------------------------------
  2017.     Key 
  2018.     
  2019.     Handle a key down event for an article window.
  2020.             
  2021.     Entry:    wind = pointer to article window.
  2022.             theChar = ASCII code of key.
  2023.             theKey = keyboard code of key.
  2024.             modifiers = modifiers field from event record.
  2025.             
  2026.     Exit:    function result = error code.
  2027. ----------------------------------------------------------------------------*/
  2028.  
  2029. static OSErr Key (WindowPtr wind, unsigned char theChar, unsigned char theKey, 
  2030.     short modifiers)
  2031. {
  2032.     TWindow **info;
  2033.     ControlHandle vScroll, hScroll;
  2034.     TEHandle theTE;
  2035.     OSErr err = noErr;
  2036.     short scrollIntoView;
  2037.     TKeypadKey keypadKey;
  2038.     Boolean isArrow;
  2039.  
  2040.     info = (TWindow**)GetWRefCon(wind);
  2041.     vScroll = (**info).vScroll;
  2042.     hScroll = (**info).hScroll;
  2043.     theTE = (**info).theTE;
  2044.     isArrow = IsArrowKey(theChar);
  2045.     
  2046.     if ((modifiers & cmdKey) != 0 && !isArrow) {
  2047.         SysBeep(0);
  2048.         return noErr;
  2049.     }
  2050.  
  2051.     if (gPrefs.keypadShortcuts && IsKeypadKey(theChar, theKey, &keypadKey)) {
  2052.         switch (keypadKey) {
  2053.             case kKeypadEqualKey:
  2054.                 DoSelectAll(wind);
  2055.                 return noErr;
  2056.             case kKeypadStarKey:
  2057.                 return DoClose(wind);
  2058.             case kKeypadMinusKey:
  2059.                 return DoMarkCommand(wind, false);
  2060.             case kKeypadPlusKey:
  2061.                 return DoMarkCommand(wind, true);
  2062.             case kKeypadEnterKey:
  2063.                 return DoNextGroup(wind);
  2064.             case kKeypadPeriodKey:
  2065.                 return DoNextThread(wind);
  2066.             case kKeypad0Key:
  2067.                 return DoNextArticle(wind);
  2068.             case kKeypad1Key:
  2069.                 TEScrollScrollByPartCode(theTE, vScroll, kScrollToEnd);
  2070.                 return noErr;
  2071.             case kKeypad2Key:
  2072.                 TEScrollScrollByPartCode(theTE, vScroll, inDownButton);
  2073.                 return noErr;
  2074.             case kKeypad3Key:
  2075.                 TEScrollScrollByPartCode(theTE, vScroll, inPageDown);
  2076.                 return noErr;
  2077.             case kKeypad4Key:
  2078.                 if (hScroll != nil) {
  2079.                     ScrollActionSection(hScroll, inUpButton);
  2080.                 } else {
  2081.                     SysBeep(0);
  2082.                 }
  2083.                 return noErr;
  2084.             case kKeypad5Key:
  2085.                 if (GetControlValue(vScroll) < GetControlMaximum(vScroll)) {
  2086.                     TEScrollScrollByPartCode(theTE, vScroll, inPageDown);
  2087.                 } else if (hScroll != nil && 
  2088.                     GetControlValue(hScroll) < GetControlMaximum(hScroll)) 
  2089.                 {
  2090.                     ScrollActionSection(hScroll, inDownButton);
  2091.                 } else {
  2092.                     return DoNextArticle(wind);
  2093.                 }
  2094.                 return noErr;
  2095.             case kKeypad6Key:
  2096.                 if (hScroll != nil) {
  2097.                     ScrollActionSection(hScroll, inDownButton);
  2098.                 } else {
  2099.                     SysBeep(0);
  2100.                 }
  2101.                 return noErr;
  2102.             case kKeypad7Key:
  2103.                 TEScrollScrollByPartCode(theTE, vScroll, kScrollToHome);
  2104.                 return noErr;
  2105.             case kKeypad8Key:
  2106.                 TEScrollScrollByPartCode(theTE, vScroll, inUpButton);
  2107.                 return noErr;
  2108.             case kKeypad9Key:
  2109.                 TEScrollScrollByPartCode(theTE, vScroll, inPageUp);
  2110.                 return noErr;
  2111.             default:
  2112.                 SysBeep(0);
  2113.                 return noErr;
  2114.         }
  2115.     }
  2116.     
  2117.     if (theChar == pageUpKey) {
  2118.         TEScrollScrollByPartCode(theTE, vScroll, inPageUp);
  2119.         return noErr;
  2120.     }
  2121.     if (theChar == pageDownKey) {
  2122.         TEScrollScrollByPartCode(theTE, vScroll, inPageDown);
  2123.         return noErr;
  2124.     }
  2125.     if (theChar == homeKey) {
  2126.         TEScrollScrollByPartCode(theTE, vScroll, kScrollToHome);
  2127.         return noErr;
  2128.     }
  2129.     if (theChar == endKey) {
  2130.         TEScrollScrollByPartCode(theTE, vScroll, kScrollToEnd);
  2131.         return noErr;
  2132.     }
  2133.     
  2134.     if (isArrow) {
  2135.         TEArrowKey(theChar, modifiers, theTE, 0, &gPrevEvent, &scrollIntoView);
  2136.         TEScrollScrollRangeIntoView(theTE, scrollIntoView, scrollIntoView, vScroll);
  2137.         return noErr;
  2138.     }
  2139.     
  2140.     if (gPrefs.keyboardShortcuts) {
  2141.     
  2142.         if (theChar == ' ') {
  2143.             if (GetControlValue(vScroll) < GetControlMaximum(vScroll)) {
  2144.                 TEScrollScrollByPartCode(theTE, vScroll, inPageDown);
  2145.             } else if (hScroll != nil && GetControlValue(hScroll) < GetControlMaximum(hScroll)) {
  2146.                 ScrollActionSection(hScroll, inDownButton);
  2147.             } else {
  2148.                 return DoNextArticle(wind);
  2149.             }
  2150.             return noErr;
  2151.         }
  2152.         
  2153.         theChar = tolower(theChar);
  2154.         
  2155.         if (theChar == 'n' || theChar == 'i') {
  2156.             return DoNextArticle(wind);
  2157.         }
  2158.         
  2159.         if (theChar == 't') {
  2160.             return DoNextThread(wind);
  2161.         }
  2162.         if (theChar == 'g' || theChar == 'j') {
  2163.             return DoNextGroup(wind);
  2164.         }
  2165.         
  2166.         if (theChar == 'w') {
  2167.             return DoClose(wind);
  2168.         }
  2169.         
  2170.         if (theChar == 'u') {
  2171.             return DoMarkCommand(wind, false);
  2172.         }
  2173.         
  2174.         if (theChar == 'm') {
  2175.             return DoMarkCommand(wind, true);
  2176.         }
  2177.         
  2178.         if (theChar == 'a') {
  2179.             DoSelectAll(wind);
  2180.             return noErr;
  2181.         }
  2182.         
  2183.     }
  2184.  
  2185.     SysBeep(0);
  2186.     
  2187.     return noErr;
  2188. }
  2189.  
  2190.  
  2191.  
  2192. /*----------------------------------------------------------------------------
  2193.     Grow 
  2194.     
  2195.     Handle a mouse down event in the grow box of an article window.
  2196.     
  2197.     Entry:    wind = pointer to article window.
  2198.             where = location of mouse down event, in global coordinates.
  2199.             
  2200.     Exit:    function result = error code.
  2201. ----------------------------------------------------------------------------*/
  2202.  
  2203. static OSErr Grow (WindowPtr wind, Point where)
  2204. {
  2205.     Rect sizeRect;
  2206.     long size;
  2207.     short width, height;
  2208.     
  2209.     SetRect(&sizeRect, kMinWindowWidth, MinHeight(wind), 0x7fff, 0x7fff);
  2210.     size = GrowWindow(wind, where, &sizeRect);
  2211.     
  2212.     if (size  != 0) {
  2213.         width = LoWord(size);
  2214.         height = HiWord(size);
  2215.         FixHeight(wind, &height);
  2216.         SizeWindow(wind, width, height, false);
  2217.         ResizeContents(wind);
  2218.     }
  2219.     
  2220.     return noErr;
  2221. }
  2222.  
  2223.  
  2224.  
  2225. /*----------------------------------------------------------------------------
  2226.     Zoom
  2227.     
  2228.     Zoom an article window.
  2229.     
  2230.     Entry:    wind = pointer to article window.
  2231.             zoomDir = zoom direction = inZoomIn, inZoomOut, or
  2232.                 zoomOutOnlyIfBigger.
  2233.             
  2234.     Exit:    function result = error code.
  2235. ----------------------------------------------------------------------------*/
  2236.  
  2237. static OSErr Zoom (WindowPtr wind, short zoomDir)
  2238. {
  2239.     TWindow **info;
  2240.     short width, height, lineHeight, minHeight, lineWidth, maxWidth, maxBodyLineWidth;
  2241.     Rect zoomRect, r;    
  2242.     WStateData **wState;
  2243.     FontInfo fInfo;
  2244.     TEHandle theTE, tempTE = nil;
  2245.     Handle fullText;
  2246.     long numBodyLines, longHeight, offset, length, maxHeight;
  2247.     char *p, *pEnd, *lineStart;
  2248.     long **breaks;
  2249.     short numSections, i;
  2250.     OSErr err = noErr;
  2251.     char state;
  2252.  
  2253.     wState = (WStateData**)((WindowPeek)wind)->dataHandle;
  2254.     if (zoomDir == inZoomOut || zoomDir == zoomOutOnlyIfBigger) {
  2255.         info = (TWindow**)GetWRefCon(wind);
  2256.         GetFontInfo(&fInfo);
  2257.         lineHeight = fInfo.leading + fInfo.ascent + fInfo.descent;
  2258.         theTE = (**info).theTE;
  2259.         fullText = (**info).fullText;
  2260.         maxBodyLineWidth = (**info).maxBodyLineWidth;
  2261.         numBodyLines = (**info).numBodyLines;
  2262.         
  2263.         if (maxBodyLineWidth == 0) {
  2264.             maxWidth = 80 * fInfo.widMax;
  2265.             numBodyLines = 0;
  2266.             state = MyHGetState(fullText);
  2267.             MyHLock(fullText);
  2268.             for (p = *fullText + FindBody(fullText), 
  2269.                     pEnd = *fullText + MyGetHandleSize(fullText); 
  2270.                 p < pEnd; 
  2271.                 p++) 
  2272.             {
  2273.                 lineStart = p;
  2274.                 while (p < pEnd && *p != CR) p++;
  2275.                 numBodyLines++;
  2276.                 if (maxBodyLineWidth < maxWidth) {
  2277.                     lineWidth = TextWidth(lineStart, 0, p - lineStart);
  2278.                     if (lineWidth > maxBodyLineWidth) maxBodyLineWidth = lineWidth;
  2279.                 }
  2280.             }
  2281.             MyHSetState(fullText, state);
  2282.             if (maxBodyLineWidth > maxWidth) maxBodyLineWidth = maxWidth;
  2283.             (**info).maxBodyLineWidth = maxBodyLineWidth;
  2284.             (**info).numBodyLines = numBodyLines;
  2285.         }
  2286.         width = maxBodyLineWidth + 2*kTextMargin + 18;
  2287.         if (width < kMinWindowWidth) width = kMinWindowWidth;
  2288.         
  2289.         if (numBodyLines > 100) {
  2290.             height = 0x7fff;
  2291.         } else {
  2292.             CalculateZoomRect(wind, width, 0x7fff, &zoomRect, gPrefs.dontCoverFinderIcons);
  2293.             SetRect(&r, 0, 0, zoomRect.right - zoomRect.left - 15 - 2*kTextMargin, 0x7fff);
  2294.             tempTE = TENew(&r, &r);
  2295.             breaks = (**info).sectionBreaks;
  2296.             numSections = (**info).numSections;
  2297.             maxHeight = 0;
  2298.             for (i = 0; i < numSections; i++) {
  2299.                 offset = (*breaks)[i];
  2300.                 length = (*breaks)[i+1] - offset;
  2301.                 if (i == 0 && !(**info).showDetails) {
  2302.                     offset = FindBody(fullText);
  2303.                     if (offset > length) offset = length;
  2304.                     length -= offset;
  2305.                 }
  2306.                 state = MyHGetState(fullText);
  2307.                 MyHLock(fullText);
  2308.                 err = MyTESetText(*fullText + offset, length, tempTE);
  2309.                 MyHSetState(fullText, state);
  2310.                 if (err != noErr) goto exit;
  2311.                 longHeight = (long)TEScrollNumTELines(tempTE) * (long)lineHeight;
  2312.                 longHeight += (**info).panelHeight + 15 + 2*kTextMargin;
  2313.                 if (longHeight > maxHeight) maxHeight = longHeight;
  2314.             }
  2315.             TEDispose(tempTE);
  2316.             if (maxHeight > 0x7fff) {
  2317.                 height = 0x7fff;
  2318.             } else {
  2319.                 height = maxHeight;
  2320.                 minHeight = MinHeight(wind);
  2321.                 if (height < minHeight) height = minHeight;
  2322.             }
  2323.         }
  2324.         
  2325.         if (zoomDir == zoomOutOnlyIfBigger) {
  2326.             if (width <= wind->portRect.right && height <= wind->portRect.bottom) {
  2327.                 SetWindowNeedsZooming(wind);
  2328.                 return noErr;
  2329.             }
  2330.             if (width < wind->portRect.right) width = wind->portRect.right;
  2331.             if (height < wind->portRect.bottom) height = wind->portRect.bottom;
  2332.         }
  2333.         
  2334.         CalculateZoomRect(wind, width, height, &zoomRect, gPrefs.dontCoverFinderIcons);
  2335.         height = zoomRect.bottom - zoomRect.top;
  2336.         FixHeight(wind, &height);
  2337.         zoomRect.bottom = zoomRect.top + height;
  2338.         (**wState).stdState = zoomRect;
  2339.         if (WindRectEqualRect(wind, &zoomRect)) return noErr;
  2340.     }
  2341.     
  2342.     EraseRect(&wind->portRect);
  2343.     ZoomWindow(wind, zoomDir, false);
  2344.     ResizeContents(wind);
  2345.     return noErr;
  2346.     
  2347. exit:
  2348.  
  2349.     if (tempTE != nil) TEDispose(tempTE);
  2350.     return err;
  2351. }
  2352.  
  2353.  
  2354.  
  2355. /*----------------------------------------------------------------------------
  2356.     Command 
  2357.     
  2358.     Handle a command for an article window.
  2359.             
  2360.     Entry:    wind = pointer to article window.
  2361.             menu = the menu.
  2362.             item = the item.
  2363.             modifiers = modifiers field from event record.
  2364.     
  2365.     Exit:    function result = error code.
  2366. ----------------------------------------------------------------------------*/
  2367.  
  2368. static OSErr Command (WindowPtr wind, short menu, short item, short modifiers)
  2369. {
  2370.     OSErr err = noErr;
  2371.  
  2372.     switch (menu) {
  2373.                 
  2374.         case kFileMenu:
  2375.         
  2376.             switch (item) {
  2377.                 case kSaveItem:
  2378.                     err = DoSave(wind, modifiers);
  2379.                     break;
  2380.                 case kSaveAsItem:
  2381.                     err = DoSaveAs(wind, modifiers);
  2382.                     break;
  2383.                 case kAppendItem:
  2384.                     err = DoAppend(wind, modifiers);
  2385.                     break;
  2386.                 case kPrintItem:
  2387.                     err = DoPrint(wind, modifiers);
  2388.                     break;
  2389.             }
  2390.             break;
  2391.             
  2392.         case kEditMenu:
  2393.  
  2394.             switch (item) {
  2395.                 case kCopyItem:
  2396.                     DoCopy(wind);
  2397.                     break;
  2398.                 case kSelectAllItem:
  2399.                     DoSelectAll(wind);
  2400.                     break;
  2401.                 case kFindItem:
  2402.                     err = DoFind(wind);
  2403.                     break;
  2404.                 case kFindAgainItem:
  2405.                     err = DoFindAgain(wind);
  2406.                     break;
  2407.                 case kEnterSelectionItem:
  2408.                     err = DoEnterSelection(wind);
  2409.                     break;
  2410.                 case kShowHideDetailsItem:
  2411.                     err = DoShowHideDetails(wind);
  2412.                     break;
  2413.                 case kRot13Item:
  2414.                     DoRot13(wind);
  2415.                     break;
  2416.             }
  2417.             break;
  2418.  
  2419.         case kNewsMenu:
  2420.         
  2421.             switch (item) {
  2422.                 case kNextArticleItem:
  2423.                     err = DoNextArticle(wind);
  2424.                     break;
  2425.                 case kNextThreadItem:
  2426.                     err = DoNextThread(wind);
  2427.                     break;
  2428.                 case kNextGroupItem:
  2429.                     err = DoNextGroup(wind);
  2430.                     break;
  2431.                 case kMarkReadItem:
  2432.                     err = DoMarkCommand(wind, true);
  2433.                     break;
  2434.                 case kMarkUnreadItem:
  2435.                     err = DoMarkCommand(wind, false);
  2436.                     break;
  2437.                 case kReplyItem:
  2438.                     err = DoReply(wind, modifiers);
  2439.                     break;
  2440.                 case kForwardItem:
  2441.                     err = DoForward(wind, modifiers);
  2442.                     break;
  2443.                 case kRedirectItem:
  2444.                     err = DoRedirect(wind, modifiers);
  2445.                     break;
  2446.             }
  2447.             break;
  2448.  
  2449.         case kSpecialMenu:
  2450.             switch (item) {
  2451.                 case kExtractBinariesItem:
  2452.                     err = DoExtractBinaries(wind, modifiers);
  2453.                     break;
  2454.                 case kSearchItem:
  2455.                     err = DoSearch(wind);
  2456.                     break;
  2457.                 case kOpenAllReferencesItem:
  2458.                     err = DoOpenAllReferences(wind);
  2459.                     break;
  2460.                 case kCancelArticleItem:
  2461.                     err = DoCancelArticle(wind);
  2462.                     break;
  2463.             }
  2464.             break;
  2465.      }
  2466.      
  2467.      return err;
  2468. }
  2469.  
  2470.  
  2471.  
  2472. /*----------------------------------------------------------------------------
  2473.     Close 
  2474.     
  2475.     Close an article window.
  2476.             
  2477.     Entry:    wind = pointer to article window.
  2478.     
  2479.     Exit:    function result = error code.
  2480. ----------------------------------------------------------------------------*/
  2481.  
  2482. static OSErr Close (WindowPtr wind)
  2483. {
  2484.     TWindow **info;
  2485.  
  2486.     info = (TWindow**)GetWRefCon(wind);
  2487.     
  2488.     RemoveChild((**info).parentWindow, wind);
  2489.     
  2490.     if ((**info).theTE != nil) TEDispose((**info).theTE);
  2491.     MyDisposeHandle((**info).fullText);
  2492.     MyDisposeHandle((**info).sectionBreaks);
  2493.     MyDisposeHandle((**info).msgId);
  2494.     MyDisposeHandle(info);
  2495.  
  2496.     MyDisposeWindow(wind);
  2497.     
  2498.     return noErr;
  2499. }
  2500.  
  2501.  
  2502.  
  2503. /*----------------------------------------------------------------------------
  2504.     Idle 
  2505.     
  2506.     Handle idle time tasks for an article window.
  2507.             
  2508.     Entry:    wind = pointer to article window.
  2509.     
  2510.     Exit:    cursorRgn = cursor region for WaitNextEvent mouse moved events.
  2511. ----------------------------------------------------------------------------*/
  2512.  
  2513. static void Idle (WindowPtr wind, RgnHandle cursorRgn)
  2514. {
  2515.     TWindow **info;
  2516.     TEHandle theTE;
  2517.     Rect r;
  2518.     Point where;
  2519.     unsigned long newsEnabled, editEnabled, specialEnabled;
  2520.     CStr255 refs;
  2521.     short selStart, selEnd;
  2522.  
  2523.     info = (TWindow**)GetWRefCon(wind);
  2524.     theTE = (**info).theTE;
  2525.     TEIdle(theTE);
  2526.     
  2527.     GetTextRect(wind, &r);
  2528.     InsetRect(&r, -kTextMargin, 0);
  2529.     LocalToGlobalRect(&r);
  2530.     RectRgn(cursorRgn, &r);
  2531.     if (gHaveDragMgr) SubtractTEHiliteRgn(cursorRgn, theTE);
  2532.     GetMouse(&where);
  2533.     LocalToGlobal(&where);
  2534.     if (PtInRgn(where, cursorRgn)) {
  2535.         SetCursor(&gIBeamCurs);
  2536.     } else {
  2537.         SetCursor(&qd.arrow);
  2538.         ComplementRgn(cursorRgn);
  2539.     }
  2540.     
  2541.     newsEnabled = (**info).parentWindow == nil ? kArticleNewsEnabledNoParent :
  2542.         kArticleNewsEnabled;
  2543.     editEnabled = kArticleEditEnabled;
  2544.     specialEnabled = kArticleSpecialEnabled;
  2545.     selStart = (**theTE).selStart;
  2546.     selEnd = (**theTE).selEnd;
  2547.     if (selStart >= selEnd) {
  2548.         editEnabled &= ~(kCopyMask | kEnterSelectionMask);
  2549.     } else if (selEnd > selStart + 255) {
  2550.         editEnabled &= ~kEnterSelectionMask;
  2551.     }
  2552.     if ((**theTE).teLength == 0) editEnabled &= ~(kSelectAllMask | kRot13Mask);
  2553.     if (!FindHeaderCString((**info).fullText, "References", refs, sizeof(refs)))
  2554.         specialEnabled &= ~kOpenAllReferencesMask;
  2555.     /* Following line of code disabled because it can cause repeated disk hits
  2556.        with Internet Config.
  2557.         if (!UserIsPoster((**info).fullText)) specialEnabled &= ~kCancelArticleMask;
  2558.     */
  2559.     if (!CanExtractBinaries(wind)) specialEnabled &= ~kExtractBinariesMask;
  2560.     if (!IsURL(theTE)) specialEnabled &= ~kOpenURLMask;
  2561.     if (*gFindPattern == 0) editEnabled &= ~kFindAgainMask;
  2562.     SetMenusTo(kAppleAllEnabled, kArticleFileEnabled, editEnabled, 
  2563.         newsEnabled, specialEnabled, kArticleWindEnabled);
  2564.     SetEditMenuShowHideDetails(!(**info).showDetails);
  2565. }
  2566.  
  2567.  
  2568.  
  2569. /*----------------------------------------------------------------------------
  2570.     InitArticleDispatchTable 
  2571.     
  2572.     Initialize the dispatch table for article windows.
  2573. ----------------------------------------------------------------------------*/
  2574.  
  2575. void InitArticleDispatchTable (void)
  2576. {
  2577.     TDispatch *d;
  2578.     
  2579.     d = &gDispatch[kArticle];
  2580.     
  2581.     d->activate = Activate;
  2582.     d->update = Update;
  2583.     d->mouse = Mouse;
  2584.     d->draggable = Draggable;
  2585.     d->key = Key;
  2586.     d->grow = Grow;
  2587.     d->zoom = Zoom;
  2588.     d->command = Command;
  2589.     d->close = Close;
  2590.     d->idle = Idle;
  2591.     
  2592.     gAutoScrollUPP = NewTEClickLoopProc(AutoScroll);
  2593.     gDragAttachedFileIconSendProcUPP = NewDragSendDataProc(DragAttachedFileIconSendProc);
  2594.     gScrollActionUPP = NewControlActionProc(ScrollAction);
  2595.     gScrollActionSectionUPP = NewControlActionProc(ScrollActionSection);
  2596. }
  2597.